zoukankan      html  css  js  c++  java
  • String、StringBuilder、StringBuffer

    String:字符串常量、线程安全
    StringBuffer:字符串变量、线程安全
    StringBuilder:字符串变量、线程不安全

    CharSequence是字符序列,String,StringBuffer和StringBuilder都实现了CharSequence接口,本质上都是通过字符数组实现的。

    String 类

    String类实现了Serializable, Comparable, CharSequence接口,被final所修饰,也就是说String对象是不可变类,是线程安全的。

    字符串可以通过两种方式进行初始化:字面常量和String对象。

    字面常量:

    String a = "java";
    String b = "java";
    String c = "ja" + "va";
    
    a==b    // true;
    a==c    // true;

    变量a、b和c都指向常量池的 “java” 字符串,表达式 “ja” + “va” 在编译期间会把结果值”java”直接赋值给c。

    String对象:

    public class StringTest {
        public static void main(String[] args) {
            String a = "java";
            String c = new String("java");
            a==c    // false      
        }
    }

    对于 String c = new String("java") 这行代码,new 指令会在java堆上为String对象申请内存;然后尝试从常量池中获取”java”字符串,如果常量池中不存在,则在常量池中新建”java”字符串,并返回;最后,调用构造方法,初始化String对象。

    其中String对象中使用char数组存储字符串,变量a指向常量池的”java”字符串,变量c指向Java堆的String对象,且该对象的char数组指向常量池的”java”字符串,所以很显然 a != c,如下图所示:

    通过 “字面量 + String对象” 进行赋值会发生什么?

    public class StringTest {
        public static void main(String[] args) {
            String a = "hello ";
            String b = "world";
            String c = a + b;
            String d = "hello world";
    
            c==d    // false  
        }
    }

    字符串变量的连接动作,在编译阶段会被转化成StringBuilder的append操作。

    对于 String c = a + b 这行代码,先在Java堆上为StringBuilder对象申请内存;调用构造方法,初始化StringBuilder对象;调用append方法,添加a和b字符串;调用toString方法,生成String对象。变量c最终指向Java堆上新建String对象,变量d指向常量池的”hello world”字符串,所以 c != d。

    特殊情况:当final修饰的变量发生连接动作时,虚拟机会进行优化,将表达式结果直接赋值给目标变量.

    public class StringTest {
        public static void main(String[] args) {
            final String a = "hello ";
            final String b = "world";
            String c = a + b;
            String d = "hello world";
     
            c==d    // true
        }
    }

    String 类源码

    1、成员变量

    String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

    //用于存储字符串
    private final char value[];
     
    //缓存String的hash值
    private int hash; // Default to 0
     
    private static final long serialVersionUID = -6849794470754667710L;

    2、构造函数

    //不含参数的构造函数,一般没什么用,因为value是不可变量
    public String() {
        this.value = new char[0];
    }
     
    //参数为String类型
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
     
    //参数为char数组,使用java.utils包中的Arrays类复制
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
     
    //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
     
    //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

    3、常用方法

    String设计成不可变类的原因

    (1)字符串常量池的需要。当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

    (2)HashCode 的需要。Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

    (3)String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

    (4)因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

    要实现一个不可变类:

      既然不可变类有这么多优势,那么我们借鉴String类的设计,自己实现一个不可变类。
      不可变类的设计通常要遵循以下几个原则:

      将类声明为final,所以它不能被继承。 将所有的成员声明为私有的,这样就不允许直接访问这些成员。 对变量不要提供setter方法。 将所有可变的成员声明为final,这样只能对它们赋值一次。 通过构造器初始化所有成员,进行深拷贝(deep copy)。 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

    对于下面的代码:
    String s = "abcd";
    s = s+1;
    System.out.print(s); // result : abcd1
    首先创建对象s,赋予一个abcd,然后再创建一个新的对象用来执行第二行代码,之前对象s并没有变化。

    再比如:

    String a = "a"; //假设a指向地址0x0001
    a = "b"; //重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的。

    但是,对于:
    String str = “This is only a” + “ simple” + “test”;
    就相当于 String str = “This is only a simple test”;

    toCharArray()方法

    String s = " a b  ";
    char[] arr = s.toCharArray();
    System.out.println(arr.length); // output: 6
    System.out.println(Arrays.toString(arr)); //output: [ , a,  , b,  ,  ]

    StringBuffer

    StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 。

  • 相关阅读:
    linux权限补充:rwt rwT rws rwS 特殊权限
    关于Linux操作系统下文件特殊权限的解释
    Java学习笔记——Java程序运行超时后退出或进行其他操作的实现
    Java实现 蓝桥杯 算法提高 判断名次
    Java实现 蓝桥杯 算法提高 判断名次
    Java实现 蓝桥杯 算法提高 日期计算
    Java实现 蓝桥杯 算法提高 日期计算
    Java实现 蓝桥杯 算法提高 概率计算
    Java实现 蓝桥杯 算法提高 概率计算
    Java实现 蓝桥杯 算法提高 复数四则运算
  • 原文地址:https://www.cnblogs.com/hesier/p/5627206.html
Copyright © 2011-2022 走看看