首先在这里列出三者的特点。
- 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);
}
显然这块速度会慢不少。
结束啦,谢谢阅读。