String : 不可变字符序列
StringBuffer : 可变字符序列 线程安全
StringBuilder :可变字符序列 线程不安全
JDK11
1.可变和不可变
String 底层存储结构为final 修饰的字节数组,因此不可修改
private final byte[] value;
StringBuffer 和StringBuilder 都继承AbstractStringBuilder
底层使用的也是AbstractStringBuilder 中的字节数组,可变
/**
* The value is used for character storage.
*/
byte[] value;
2.jdk9之后String底层存储的新特性
这里先介绍一下jdk9之后的新特性
jdk9之前底层用的都是char[],9之后改用byte[]
原因:
一个char占用两个字节,一个byte占用一个字节
多数情况下String存储的都是拉丁字符,而拉丁字符占用一个字节,因此浪费了一个字节的空间,所以将char数组改为byte数组。那么对于中文占用两个字节的情况下,jdk9还有一种编码是UTF-16可以选择,因此内部需要一个标识coder来表示使用了哪种编码,LATIN1 值为0,UTF16 值为1。
String类中
private final byte coder;
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
很多方法需要区分是LATIN1编码还是UTF16编码,一般通过isLatin1来区分
3.StringBuffer StringBuilder
1.构造函数
public StringBuffer() {
super(16);
}
/**
* Constructs a string buffer with no characters in it and
* the specified initial capacity.
*
* @param capacity the initial capacity.
* @throws NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
@HotSpotIntrinsicCandidate
public StringBuffer(int capacity) {
super(capacity);
}
/**
* Constructs a string buffer initialized to the contents of the
* specified string. The initial capacity of the string buffer is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
@HotSpotIntrinsicCandidate
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
可以看到生成对象时默认空参时是16大小的空间,也可以指定具体的大小
如果有参数,就是参数的长度加上16
保证有16大小的空余空间
StringBuffer StringBuilder构造函数的源码都是一样的,都调用父类的构造函数。
AbstractStringBuilder(int capacity) {
if (COMPACT_STRINGS) {
value = new byte[capacity];
coder = LATIN1;
} else {
value = StringUTF16.newBytesFor(capacity);
coder = UTF16;
}
}
2. append() 、扩容
StringBuffer
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuilder
public StringBuilder append(String str) {
super.append(str);
return this;
}
区别就是StringBuffer 加入了 synchronized同步锁 保证了线程安全
接下来看父类的 append() 方法
public AbstractStringBuilder append(String str) {
if (str == null) {
return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
一进来先判断是否为空,为空就返回appendNull()
private AbstractStringBuilder appendNull() {
ensureCapacityInternal(count + 4);
int count = this.count;
byte[] val = this.value;
if (isLatin1()) {
val[count++] = 'n';
val[count++] = 'u';
val[count++] = 'l';
val[count++] = 'l';
} else {
count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
}
this.count = count;
return this;
}
可以看出最后返回的是原来的字符加上null字符
StringBuilder stringBuffer1 = new StringBuilder("123");
String s1=null;
StringBuilder stringBuffer2 = stringBuffer1.append(s1);
System.out.println(stringBuffer2);
>>123null
回过头来看如果不为空,就判断内部容量还够不够ensureCapacityInternal(count + len);
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
根据前面jdk9的新特性,可以看到如果编码为LATIN1 ,那么byte数组value的长度右移coder位,即0位,不变。
如果编码为UTF16 ,那么byte数组value的长度右移coder位,即1位。因为UTF16是两个字节存一个字符,而你value.length想返回的也是字符串的长度而不是byte数组的长度。
位移完之后判断容量够不够,不够就扩容,扩容你需要有新的容量大小,使用newCapacity(minimumCapacity)
minimumCapacity为原来的长度加上要拼接字符串的长度,即为拼接后的长度
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
int newCapacity = (oldCapacity << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;
return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
首先新的容积为旧的两倍+2
如果还不够,直接把minCapacity作为新的容量
下面还有一些特殊情况比如左移后变成负数或者超出安全范围
4. 使用
1.少量数据或者对数据没有大量处理操作时可以使用String
2.单线程处理数据时可以使用StringBuilder,效率更高
3.多线程处理数据时可以使用StringBuffer,保证线程安全