深入理解String、StringBuffer、StringBuilder

深入理解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;
    }

 

总结

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值