0. 说明
今天看到了一个老的面试题就是:String、StringBuffer和StringBuilder之间的区别。
这个面试题非常经典,其实现在大多数情况下都会考虑到性能为题,在我们写一个类的toString方法的时候,一般都是使用的IDE自动生成,无论生成的代码是"+"链接符,还是其他很么样式,一般情况下都会被IDE在编译的时候,变成性能相对最优的StringBuilder的append的形式(一般情况下,当然IDE具体操作可以自己定制)。
1. StringBuffer和StringBuilder
这两个类我们先从源码中分析下:
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ private transient char[] toStringCache; ... @Override public synchronized int length() { return count; } @Override public synchronized int capacity() { return value.length; } @Override public synchronized void ensureCapacity(int minimumCapacity) { super.ensureCapacity(minimumCapacity); } @Override public synchronized void trimToSize() { super.trimToSize(); } @Override public synchronized void setLength(int newLength) { toStringCache = null; super.setLength(newLength); } @Override public synchronized char charAt(int index) { if ((index < 0) || (index >= count)) throw new StringIndexOutOfBoundsException(index); return value[index]; } @Override public synchronized int codePointAt(int index) { return super.codePointAt(index); } @Override public synchronized int codePointBefore(int index) { return super.codePointBefore(index); } @Override public synchronized int codePointCount(int beginIndex, int endIndex) { return super.codePointCount(beginIndex, endIndex); } @Override public synchronized int offsetByCodePoints(int index, int codePointOffset) { return super.offsetByCodePoints(index, codePointOffset); } @Override public synchronized void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin){ super.getChars(srcBegin, srcEnd, dst, dstBegin); } @Override public synchronized void setCharAt(int index, char ch) { if ((index < 0) || (index >= count)) throw new StringIndexOutOfBoundsException(index); toStringCache = null; value[index] = ch; } @Override public synchronized StringBuffer append(Object obj) { toStringCache = null; super.append(String.valueOf(obj)); return this; } @Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } public synchronized StringBuffer append(StringBuffer sb) { toStringCache = null; super.append(sb); return this; } @Override synchronized StringBuffer append(AbstractStringBuilder asb) { toStringCache = null; super.append(asb); return this; } @Override public synchronized StringBuffer append(CharSequence s) { toStringCache = null; super.append(s); return this; } @Override public synchronized StringBuffer append(CharSequence s, int start, int end){ toStringCache = null; super.append(s, start, end); return this; } @Override public synchronized StringBuffer append(char[] str) { toStringCache = null; super.append(str); return this; } @Override public synchronized StringBuffer append(char[] str, int offset, int len) { toStringCache = null; super.append(str, offset, len); return this; } @Override public synchronized StringBuffer append(boolean b) { toStringCache = null; super.append(b); return this; } @Override public synchronized StringBuffer append(char c) { toStringCache = null; super.append(c); return this; } @Override public synchronized StringBuffer append(int i) { toStringCache = null; super.append(i); return this; } @Override public synchronized StringBuffer appendCodePoint(int codePoint) { toStringCache = null; super.appendCodePoint(codePoint); return this; } @Override public synchronized StringBuffer append(long lng) { toStringCache = null; super.append(lng); return this; } @Override public synchronized StringBuffer append(float f) { toStringCache = null; super.append(f); return this; } @Override public synchronized StringBuffer append(double d) { toStringCache = null; super.append(d); return this; } @Override public synchronized StringBuffer delete(int start, int end) { toStringCache = null; super.delete(start, end); return this; } @Override public synchronized StringBuffer deleteCharAt(int index) { toStringCache = null; super.deleteCharAt(index); return this; } @Override public synchronized StringBuffer replace(int start, int end, String str) { toStringCache = null; super.replace(start, end, str); return this; } @Override public synchronized String substring(int start) { return substring(start, count); } @Override public synchronized CharSequence subSequence(int start, int end) { return super.substring(start, end); } @Override public synchronized String substring(int start, int end) { return super.substring(start, end); } @Override public synchronized StringBuffer insert(int index, char[] str, int offset, int len){ toStringCache = null; super.insert(index, str, offset, len); return this; } @Override public synchronized StringBuffer insert(int offset, Object obj) { toStringCache = null; super.insert(offset, String.valueOf(obj)); return this; } @Override public synchronized StringBuffer insert(int offset, String str) { toStringCache = null; super.insert(offset, str); return this; } @Override public synchronized StringBuffer insert(int offset, char[] str) { toStringCache = null; super.insert(offset, str); return this; } @Override public StringBuffer insert(int dstOffset, CharSequence s) { super.insert(dstOffset, s); return this; } @Override public synchronized StringBuffer insert(int dstOffset, CharSequence s, int start, int end){ toStringCache = null; super.insert(dstOffset, s, start, end); return this; } @Override public StringBuffer insert(int offset, boolean b) { super.insert(offset, b); return this; } @Override public synchronized StringBuffer insert(int offset, char c) { toStringCache = null; super.insert(offset, c); return this; } @Override public StringBuffer insert(int offset, int i) { super.insert(offset, i); return this; } @Override public StringBuffer insert(int offset, long l) { super.insert(offset, l); return this; } @Override public StringBuffer insert(int offset, float f) { super.insert(offset, f); return this; } @Override public StringBuffer insert(int offset, double d) { super.insert(offset, d); return this; } @Override public int indexOf(String str) { return super.indexOf(str); } @Override public synchronized int indexOf(String str, int fromIndex) { return super.indexOf(str, fromIndex); } @Override public int lastIndexOf(String str) { return lastIndexOf(str, count); } @Override public synchronized int lastIndexOf(String str, int fromIndex) { return super.lastIndexOf(str, fromIndex); } @Override public synchronized StringBuffer reverse() { toStringCache = null; super.reverse(); return this; } @Override public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }...}
题外话:看看这源码,实在是规范,当一行长度达到一定数量的时候,就折行,这样阅读起来很方便,可读性非常强,笔者是真的被同事的超级长的一行代码给弄的怀疑人生过。
再看看StringBuilder的源码
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{...// 具体源码请各位自己查看}
从源码中可知StringBuffer和StringBuilder都继承了AbstractStringBuilder抽象类,看下AbstractStringBuilder抽象类的源码:
abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count;...}
从上面的源码中可知两个实现类都继承AbstractStringBuilder类,也就继承了value的属性,可知其内部存储数据是以数组的形式存储的。
至于两个类的区别,从源码中也可以很容易的看出来,那就是StringBuffer大部分方法都有synchronized关键字的修饰,从而保证是线程安全的,而StringBuilder是不安全的。
2. String和StringBuilder
StringBuffer和StringBuilder在前文分析中可知,基本上是一样的,唯一的区别就是是否是线程安全的,所以本文在讲String与前两者的区别的时候,我们只讲String和StringBuilder的区别,因为String和StringBuffer的区别与其类似。
具体源码请各位移步到自己电脑的IDE中查看,他们具有的方法都不相同了,但是功能基本上想起,先说下String,String有一个特性就是是一个不可变类,说不可变并不是说不能为一个String类型的变量重新赋,只是说重新赋值的时候,换了对应,也就是String变量指向了新的String对象,而老的对象可能被回收掉。我们看图分析下。对于一个初始为"test"的字符串,在连接另外一个"test"之后对象发生的变化。
![24b760a95fb051d1d26389ee7ec73515.png](https://i-blog.csdnimg.cn/blog_migrate/e23c787535a1b20753ee8086e8168cd0.jpeg)
String和StringBuilder
从图中可以看到当原test字符串cat上新的test变为testtest之后的变化,String方式是新建一个String对象重新指向新的testtest,并将str指向新的String 对象,而StringBuilder并不是这样的,StringBuilder对象并没有新建,而是StringBuilder指向的数组发生了变化,这里笔者画的是新建一个字符数组,其实是一种特殊的情况,这里边也有类似集合扩容的概念,就是初始的时候字符数组有一定的长度,源码中默认的是16。而后在append的时候,如果数组能够满足append之后的存储,那么就不会新建字符数组,否则扩容新建字符数组。所以StringBuilder在实际操作中减少了很多新建对象的操作,也一定量的减少了GC的操作,性能比较String有很多提升。各位看客可以在IDE中DEBUG下看看对象的对象是不是引用发生了变化。我下面把我写的test代码贴出来:
@Testpublic void test_string() { String str = new String("test"); System.out.println(str); str = str + "test"; System.out.println(str); StringBuilder sb = new StringBuilder("test"); System.out.println(sb.toString()); sb.append("test"); System.out.println(sb);}
笔者在IDE中操作的时候,第4行和第6行的str内部value的对象不是同一个,而第9行和第11行的sb的value是同一个。
3. 总结
String、StringBuffer和StringBuilder好像是一个绕不过去的面试话题,本文主要介绍了他们之间的区别,因为他们的功能基本上都很相似。区别表现在性能和线程安全上,如果希望是线程安全的就是用StringBuffer,如果是希望高性能那就选择StringBuilder,如果要是String不可变,就使用String(外加final修饰,因为其他的前两个sb,即便是final修饰,其仍然可以append,会改变真实的String值)。
希望本文对各位看官理解和选择String、StringBuffer和StringBuilder时有所帮助。
求**评论、点赞、关注+转发**
限于笔者知识有限,如果不足之处请帮忙指正,不喜勿喷!
您的支持是我不懈努力的动力,请读者多支持下!
更多文章,请关注微信公众号 CS_Toper之路,或者头条号 CSToper。