首先在这里列出三者的特点。
- String:不可变的字符序列,效率低。
- StringBuilder:可变,线程不安全,效率高。
- StringBuffer:可变,线程不安全,效率较高。
三者的效率:StringBuilder > StringBuffer > String
String
首先我们看看String的内部结构。
public final class String
利用final关键字修饰类,使得String不得被继承。
private final char value[];
维护了一个private final修饰的字符数组,使得value的指向不可变。
这是String类不可变的主要原因。
- private使得value数组不能在类外访问,也就无法修改其中某个字符(虽然利用反射可以修改)
- final使得value指向不能改变,使得所有对字符串修改的操作,都会重新创建String对象,重新设置value数组。
这也就保证了String的安全性。
有同学可能会思考,既然不可变会影响效率,为什么还要这样设计呢?
想想看,如果说String类型的变量s存在某个容器中,这时,在其他地方对s进行更改,我们其实是并不希望容器中的s也发生变化的。通常使用字符串的方法,如replace,substring等,我们都是不需要他本身发生改变的。
StringBuilder
因为String在发生变化的时候都会创建对象,有时候就特别影响效率。
比如在循环中使用+操作。
比如以下代码,大家可以去测试一下。速度差了相当多。
public static void main(String[] args) {
String s1 = new String();
StringBuilder s2 = new StringBuilder();
long start = System.currentTimeMillis();
for (int i = 0; i < 40000; i++) {
s1 += i;
}
long end = System.currentTimeMillis();
System.out.println("String: " + (end - start));
start = System.currentTimeMillis();
for (int i = 0; i < 40000; i++) {
s2.append(i);
}
end = System.currentTimeMillis();
System.out.println("StringBuilder: " + (end - start));
}
String: 4600ms
StringBuilder: 1ms
本质上就是因为StringBuilder底层的字符数组是char[] value,没有关键词修饰,所以可以直接修改。
所以说当字符串需要进行修改操作时,我们可以创建一个StringBuilder来帮助操作。
常用的操作有:
- 增:append()
- 删:delete()
- 改:setCharAt(int n, char ch) replace(int start, int end, String str)
- 插:insert(int offset, xxx)
- 反转:reverse()
剩下的操作和String一样。
StringBuffer
与StringBuilder不同的是保证线程安全, 使用synchronized关键字来保证的线程安全。
还有一点,看源码发现StringBuffer多了一个变量。
private transient char[] toStringCache;
//toString返回的最后一个值的缓存。每当修改StringBuffer时清除。
它的存在是为了让StringBuffer的toString操作更快。
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
这是StringBuffer的toString方法,另外,在对StringBuffer对象修改时都会加一句**toStringCache = null;**所以说toStringCache的存在是让连续的toString()操作变快,具体为什么快呢?看它调用的构造参数
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
是直接赋值将toStringCache赋值给String的value数组。很巧妙的方法,用空间换取了时间。对比一下StringBuilder调用的构造方法吧。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
显然这块速度会慢不少。
结束啦,谢谢阅读。
本文详细介绍了Java中String、StringBuilder和StringBuffer的区别与性能。String是不可变的,适合不可变的场景,而StringBuilder和StringBuffer在需要修改字符串时更为高效,其中StringBuilder是非线程安全但效率更高,StringBuffer则通过同步机制保证线程安全但牺牲了部分性能。在循环构建字符串等操作中,StringBuilder的表现优于String。文章还探讨了为何要设计成不可变以及如何选择合适的字符串操作类。





