文章目录
前言
上一篇分析了AbstractStringBuilder抽象类,在面试中常常会问到String和StringBuilder、StringBuffer的对比,网上很多博客都给出了三者的对比结果,这里借用张老师的分析结果。

其实String、StringBuilder、StringBuffer的对比主要就以下几点:
- StringBuilder和StringBuffer为啥可变,String为啥不可变
- 为什么说StringBuilder线程不安全,String和StringBuffer线程安全
下面带着这三个问题,深入源码,我们去探究原因。
1.StringBuilder和StringBuffer为啥可变,String为啥不可变
我们知道无论是String、StringBuilder还是StringBuffer其实际存放数据最终都是通过成员变量value这一字符数组实现的。那先看一下value的源码定义。
-
String
private final char value[];
String中value是定义了private final,private使得value是私有的,并且String类没有提供外部访问value的方法,确保了value不能被其他类访问。
关于final的一些解释,在之前写的String源码解读的博客中有阐述,这里引用一下结论:总得来说对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改(内容不可变);如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象(指针不可变,内容其实是可变的)。我们再看String的value,value是引用类型,因此其指向的字符数组是不可变。
正是由于String实际数据存储的字符数组是private final修饰的,并未提供外部可访问的方法,因此String是不可变的。
补充
-
String并非绝对不可变,前面说了final修饰的引用类型,指向地址不可变,但指向的内容是可变的,因此如果通过反射的方式获取到value,然后直接操作value,这样就能改变String。
-
String经常给人可变的错觉。例如下面例子,感觉就是String改变了内容。
String a = "123"; a="abc";
String可变的这个错觉其实是因为对 对象引用 和对象 的理解不透彻。 对象是内存中的实际的一块内存区域,对象引用是指向这块内存区域的一个指针。上面的例子中,a是对象引用,而字符串”123“和”abc“都是实际的对象,这个例子只是改变了a的指向的内存地址,并不是改变了a指向的对象的内容(两个对象都还存在的)。
-
String的类定义声明String是final修饰的,但这不是String不可变的原因,只能说明String不能被继承。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}
-
-
AbstractStringBuilder
StringBuffer和StringBuilder为什么可变,其原因是在他们的父类中有体现。
/** * The value is used for character storage. */ char[] value; /** * The count is the number of characters used. */ int count;
AbstractStringBuilder可变的原因是:
AbstractStringBuilder定义的数据存放的char数组,并未修饰final,并提供了两个指针,capacity(value的总长度)和length(在char数组中已经使用了的长度),**不同于String new一个新的String的方式来“变化”char数组;AbstractStringBuilder是通过往同一个char数组中“塞”的方式去实现可变的。**怎么塞的和扩容的问题可以看之前的博客.
2. 为什么说StringBuilder线程不安全,String和StringBuffer线程安全
String由于其不可变的性质,所以是线程安全的。
Stringbuilder和StringBuffer的实现基本相同,最大的不同在于StringBuffer的方法都加上了synchronized,因此保证了线程安全,但也损失了效率。Stringbuilder由于没有加锁的处理,多线程进行append等操作时会出现线程不安全的问题。
补充
StringBuilder和StringBuffer初始大小和扩容
这两个类的初始大小都一样,
- 对于指定了capacity的(传入参数int),大小直接就是capacity;
- 无参的,capacity=16;
- 参数是String或CharSequence,capacity=str/seq.length+16;
扩容都是调用的父类AbstractStringBuilder的扩容方法,参考之前的博客。