String、Stringbuffer和StringBuilder的异同? String 为什么是不可变的?
1.可变性
简单的来说:String
类中使用 final 关键字修饰字符数组char[]来保存字符串,private final char value[]
,所以String
对象是不可变的。
补充:在 Java 9 之后,String 、
StringBuilder
与StringBuffer
的实现改用 byte 数组存储字符串private final byte[] value
为什么使用byte字节而舍弃了char字符:
节省内存占用,byte占一个字节(8位),char占用2个字节(16),相较char节省一半的内存空间。节省gc压力。
针对初始化的字符,对字符长度进行判断选择不同的编码方式。如果是 LATIN-1 编码,则右移0位,数组长度即为字符串长度。而如果是 UTF16 编码,则右移1位,数组长度的二分之一为字符串长度。
而 StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串char[] value
但是没有用 final
关键字修饰,所以这两种对象都是可变的。
StringBuilder
与 StringBuffer
的构造方法都是调用父类构造方法也就是AbstractStringBuilder
实现的.
2.线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是 StringBuilder
与 StringBuffer
的公共父类,定义了一些字符串的基本操作,如 expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
3.性能
每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象。StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
内存源码解析:
String类
//底层是final修饰的字符数组 //char[0] 长度为0
String a=new String();
//char[3]={'a','b','c'} 长度为3
String b=new String("abc");
而StringBuffer
和Stringbuilder
两个类都继承自 AbstractStringBuilder
类,都调用了父类的构造方法,而父类使用的是字符数组char []
StringBuffer
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuilder
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
从源码中可以看出无参构造StringBuffer stringBuffer = new StringBuffer();
和StringBuilder stringBuilder = new StringBuilder();
默认的初始化容量是16的字符数组
StringBuffer
public StringBuffer() {
super(16);
}
StringBuilder
public StringBuilder() {
super(16);
}
而有参构造分为三种,一种是指定长度,另外一种是传入字符串,还有一种是传入字符序列
StringBuffer
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuilder
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
从源码中可见,如果传入的是int数值,则创建该对应长度的char[]数组,如果是字符串和字符序列CharSequence
则创建传入的字符串或者字符序列的长度+16
的字符数组
同时,因为StringBuffer
和StringBuilder
是可变的字符序列,所以就会牵扯到char[]字符数组的扩容问题
?
这里以StringBuffer
为例:
首先从有参构造
点开来看:
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
点开append
方法:发现StringBuffer
重写了父类的append方法,并调用父类的append方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
点开super.append(str);
方法,看到了ensureCapacityInternal(count + len);这个确保内部容量的方法,cout为最小容量
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
点进去ensureCapacityInternal
方法:minimumCapacity=当前剩余最小容量+当前输入的字符串长度
如果minimumCapacity大于当前字符数组的长度,则需要扩容,并把之前的内容拷贝到新字符数组中
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
点开newCapacity
方法:
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
从源码可见,扩容的新字符数组的长度
为当前字符数组的长度左移一位,并加2
,也就是当前的字符数组长度✖️2+2
如果minCapacity最小容量小于零或大于Integer.MAX_VALUE,则所需的最小容量@抛出OutOfMemoryError
点开hugeCapacity(minCapacity)
方法:
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
发现如果Integer.MAX_VALUE-当前最小容量小于0,则抛出内存不足异常OutOfMemoryError()
否则就判断当前最小容量是否大于MAX_ARRAY_SIZE
,如果大于就返回最小容量,小于就返回 ( 231-1)-8
Integer.MAX_VALUE
= 231-1
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;