深入理解String、StringBuffer、StringBuilder

    xiaoxiao2022-07-13  149

    深入理解String、StringBuffer、StringBuilder

    String

    概述:

    String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法

    常见代码:

    public class Main {     public static void main(String[] args) {         String str1 = "hello world";         String str2 = new String("hello world");         String str3 = "hello world";         String str4 = new String("hello world");                   System.out.println(str1==str2);         System.out.println(str1==str3);         System.out.println(str2==str4);     } }

    在上述代码中,String str1 = "hello world";和String str3 = "hello world"; 都在编译期间生成了 字面常量和符号引用,运行期间字面常量"hello world"被存储在运行时常量池(当然只保存了一份)。通过这种方式来将String对象跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。

    通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。

    面试常问

    public static void main (String[] args){ String a = "Hello"; String b = "World"; String c = a + b; }

    这个可以用javap -c命令反编译生成的class文件进行验证。

    发现String c是new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象。因此效率低

    public static void main (String[] args){ String a = "Hello"; String b = "World"; String c = "Hello" + "World"; }

    这个可以用javap -c命令反编译生成的class文件进行验证。

    发现String c直接等于"HelloWorld",编译期间会优化。面试中,很多面试官会觉得创造了3个对象,其实java编译优化,只创建一个对象

    StringBuffer、StringBuilder

    StringBuffer和StringBuilder类都继承了抽象类AbstractStringBuilder类;StringBuffer的方法上都加了synchronized关键字。因此,StringBuffer是线程安全的,效率慢。StringBuilder没有加synchronized关键字,因此,StringBuilder是线程不安全的,效率快

    初始化

    public StringBuffer() { super(16); } public StringBuffer(String str) { super(str.length() + 16); append(str); } AbstractStringBuilder(int capacity) { value = new char[capacity]; }

    底层存储数据的Char[]数组,初始化时,该数组的长度是16。如果构造函数有新传入字符转str,则16基础上加str.length.

    扩容

    public AbstractStringBuilder append(String str) { if (str == null) str = "null"; int len = str.length(); //检查char[]数组是否需要扩容,扩容,并将原来的数据copy进去新扩容的数组中 ensureCapacityInternal(count + len); //将新添加的数据添加到StringBuilder中的char[]数组中,实现字符串的添加 str.getChars(0, len, value, count); count += len; return this; } /** *元数组char[]的扩容过程 */ void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); } /** *扩容实现 */ public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }

     

    总结

    String 类不可变,内部维护的char[] 数组长度不可变,为final修饰,String类也是final修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字符串效率低下,因为每次都会生成新的对象。StringBuilder 类内部维护可变长度char[] , 初始化数组容量为16,存在扩容, 其append拼接字符串方法内部调用System的native方法,进行数组的拷贝,不会重新生成新的StringBuilder对象。非线程安全的字符串操作类, 其每次调用 toString方法而重新生成的String对象,不会共享StringBuilder对象内部的char[],会进行一次char[]的copy操作。StringBuffer 类内部维护可变长度char[], 基本上与StringBuilder一致,但其为线程安全的字符串操作类,大部分方法都采用了Synchronized关键字修改,以此来实现在多线程下的操作字符串的安全性。其toString方法而重新生成的String对象,会共享StringBuffer对象中的toStringCache属性(char[]),但是每次的StringBuffer对象修改,都会置null该属性值。

     

    最新回复(0)