本文主要讲述 String 和 StringBuilder 的区别,以及它们的内部实现原理。
1,String 的原理
public final class String
implements java.io.Serializable, Comparable, CharSequence,Constable, ConstantDesc {
@Stable
private final byte[] value;
.....
通过源代码我们可以看到 String 类内部使用了一个 final 修饰的 byte[] 字节码数组。当我们定义一个字符串,例如 String str = “abc“,实际上等价于 String str = new String(“abc”);。
String 类的构造函数会将接受到的字符参数,拆分成多个单独的字符,每个字符有它们对应的 Unicode,并被储存为字节,存到 byte[] 数组中。以 ”abc” 为例,在字节码数组中就是 {97,98,99}
2,String 的字符串拼接原理
第一点里我们说到,String 类内部的 byte[] 数组是 final 修饰的,所以 String 字符串本身就是不可变的。
所以当我们对 String 字符串进行拼接时,底层会直接创建一个新的 byte[] 字节码数组,然后将原先的字节数组和要拼接的字节数组拷贝到新数组,最终返回一个新的 String 对象。
不过 java 底层本身也会对字符串拼接进行一些优化。
String str1 = "abc" + "def";
// 底层优化
String str1 = "abcedf";
3,StringBuilder 与 String 区别
通过观察源代码,我们可以发现 StringBuilder 底层也是使用一个 byte[] 字节码数组用来储存字符,但与 String 不同的是,StringBuilder 创建字符串时,字节码数组默认预留 16 个字符空间,即字节码数组的长度是 字符串长度 + 16。
AbstractStringBuilder(String str){ /*/
int length = str.length();
int capacity = (length < Integer.MAX_VALUE - 16) ? length + 16 : Integer.MAX_VALUE;
final byte initCoder = str.coder();
coder = initCoder;
value = (initCoder == LATIN1) ? new byte[capacity] : StringUTF16.newBytesFor(capacity);
append(str);
}
同时 StringBuilder 类内部还有一个 count 成员变量,用来维护 StringBuilder 当前的字符总数
当我们使用 append 拼接字符串时,StringBuilder 会先判断字节数组的剩余容量是否够用(字节码数组长度 – 字符数 – 拼接的字符数),若长度足够,直接将新的字符插入到字节数组中,避免了频繁创建新数组带来的性能消耗;若容量不足,就会进行数组扩容操作,不过它不会像 String 类那样,创建一个新的对象。
public AbstractStringBuilder append(String str) {/*/
if (str == null) {return appendNull();
}
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str);
count += len;
return this;
}
StringBuilder 容量判断和数组扩容
private void ensureCapacityInternal(/*/
int minimumCapacity) {
// overflow-conscious code
int oldCapacity = value.length >> coder;
if (minimumCapacity - oldCapacity> 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity) << coder);
}
}
综上所述,我们会发现,StringBuilder 在字符串拼接方面,有着比 String 类更好的性能优化。
如果你需要频繁的对数组进行增删改操作,我们通常建议是使用 StringBuilder,反之就是 String
