在 Java 中字符串的使用非常广泛, 我们常用的如: String ,StringBuffer, StringBuilder. 但是一直都没有系统的整理过他们的区别, 今天就系统的整理一下, 记录自己的学习历程.
一: String
首先我们看下 String 的源码:
public final class String implementsjava.io.Serializable, Comparable, CharSequence
从 String 的类声明上就可以发现, String 是一个类而不是基本数据类型.
既然是一个类, 那 String 的使用也很简单, 直接 new 就可以了.
String 可以通过 "+" 来完成字符串的拼接, 那我们来做个测试:
使用是很简单, 但是通过我们上面的测试发现一个问题就是, 当我们 给 str + "test" 后, 他的 hashCode 发生了变化, 说明了第 10 行的 str 已经是一个新的对象了.
在 String 的源码中维护了一个 finalchar value[], 如下:
private final charvalue[];
所以从源码中我们可以发现, value 是 final 类型的, 是不能被改变的, 所以只要改变就只能生一个新的 String 对象. 也可以说 String 类型的对象是长度不可变的, String 拼接字符串每次都要生成一个新的对象, 所以拼接字符串的效率也比较低.
二: StringBuffer
首先我们看下 StringBuffer 的源码:
- public final class StringBuffer extends AbstractStringBuilder
- implementsjava.io.Serializable, CharSequence
从源码中我们可以看出, StringBuffer 也是一个 final class, 也不能被继承. 既然是类, 那使用也一样直接 new 一个对象出来.
StringBuffer 是通过. append 来完成字符串的拼接, 那让我们来做个测试
从我们的测试来看, StringBuffer 和 String 类是不同的, 因为 StringBuffer 被修改后并没有产生新的对象, 是在之前的对象上修改的. 下面我们来看下部分源码:
- publicStringBuffer() {
- super(16);
- }
- public StringBuffer append(CharSequence s) {
- if (s == null) s = "null";
- if (s instanceofString) return this.append((String)s);
- if (s instanceofStringBuffer) return this.append((StringBuffer)s);
- return this.append(s, 0, s.length());
- }
- public synchronized StringBuffer append(CharSequence s, int start, intend)
- {
- super.append(s, start, end);
- return this;
- }
从上面的代码中可以看出来, StringBuffer 的初始容量可以容纳 16 个字符, 当该对象的实体存放的字符的长度大于 16 时, 实体容量就自动增加.
StringBuffer 的函数都是加了 Synchronized 关键字的, 所以 StringBuffer 的方法是线程安全的, 可以在多线程中使用.
总结:
如果对字符串的改变少, 使用 String;
如果对字符串修改的较多或需要线程安全就用 StringBuffer,
三: StringBuilder
首先我们看下 StringBuilder 的源码:
- public final class StringBuilder extends AbstractStringBuilder
- implements java.io.Serializable, CharSequence
从源码中我们可以看出, StringBuilder 和 StringBuffer 一样, 也是一个 final class, 也不能被继承. 既然是类, 那使用也一样直接 new 一个对象出来.
StringBuilder 是通过. append 来完成字符串的拼接, 那让我们来做个测试
从上面的测试我门可以发现 StringBuilder 类的对象能够被多次的修改, 并且不产生新对象.
下面我们来看下部分源码:
- public StringBuilder(String str) {
- super(str.length() + 16);
- append(str);
- }
- private StringBuilder append(StringBuilder sb) {
- if (sb == null) returnappend("null");
- int len = sb.length();
- int newcount = count + len;
- if(newcount> value.length)
- expandCapacity(newcount);
- sb.getChars(0, len, value, count);
- count = newcount;
- return this;
- }
从源码中我们可以发现它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问). 所以在单线程的环境下 StringBuilder 相较于 StringBuffer 有速度优势, 因为它不需要做同步处理.
StringBuffer 的默认长度是 16, StringBuilder 的默认长度是 16 + 初始化传入字符串的长度.
四: 扩容
下面是扩容的源码:
- void expandCapacity(int minimumCapacity) {
- int newCapacity = value.length * 2 + 2;
- if (newCapacity -minimumCapacity < 0)
- newCapacity = minimumCapacity;
- if (newCapacity < 0) {
- if (minimumCapacity < 0) // overflow
- throw new OutOfMemoryError();
- newCapacity = Integer.MAX_VALUE;
- }
- value =Arrays.copyOf(value, newCapacity);
- }
当我们对 StringBuffer 和 StringBuilder 进行 append 的时候, 会先判断当前的容量是否可以放下, 如果长度不够, 就会调用 expandCapacity 来进行扩容
扩容的计算公式: 现在的长度 * 2 + 2;
如果我们扩容后的长度 (newCapacity) 还是放不下 (minimumCapacity) 的长度那就直接把所需要的长度赋值给扩容后的长度(newCapacity = minimumCapacity);
因为当 int 的最大值 + 1 就会变成负数, 所以我们需要在扩容完后验证, newCapacity 的正确性防止内存溢出, 如果超过了 int 的最大长度, 那么就把 NewCapacity 设置成 Integer.MAX_VALUE,
newCapacity = Integer.MAX_VALUE;
最后把原数组 copy 到扩容后的数组中:
value = Arrays.copyOf(value,newCapacity);
总结:
当需要频繁的字符串拼接和删除时, 建议使用 StringBuffer 或 StringBuilder
在单线程的程序中, 使用 String 或 StringBuilder
在多线程的程序中, 使用 StringBuffer.
来源: http://www.jianshu.com/p/b8cfa52be50c