典型回答
String 是Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class ,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是StringBuilder。
StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。
知识扩展
关于String类
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
private int hash;
}
- String 类是 final 的,意味着它不能被子类继承
- String 类实现了 Serializable 接口,意味着它可以序列化
- String 类实现了 Comparable 接口,意味着最好不要用 ‘==’ 来比较两个字符串是否相等,而应该用 compareTo() 方法去比较
- StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候可以考虑一下String 的另外两个好兄弟,StringBuffer 和 StringBuilder ,它们俩个是可变的。
- 每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。
为什么String 是不可变的?
- String 类被 final 关键字修饰,所以它不会有子类,这就意味着没有子类可以重写它的方法,改变它的行为。
- String 类的数据存储在 byte[ ] 数组中,而这个数组也被 final 关键字修饰了,这就表示 String 对象是没法被修改的,只要初始化一次,值就确定了。
为什么要这样设计呢?
- 第一,可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。
- 第二,保证哈希值不会被频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。
- 第三,可以实现字符常量池。
由于字符串的不可变性,String对象一旦被创建后就固定不变了,对 String 对象的任何修改都不会影响到原来的字符串对象,都会生成新的字符串对象。
关于StringBuffer
由于字符串是不可变的,所以当遇到字符串拼接(尤其是使用+号操作符)的时候,就需要考量性能的问题,你不能毫无顾虑地生产太多 String 对象,对珍贵的内存造成不必要的压力。
于是 Java 就设计了一个专门用来解决此问题的 StringBuffer 类。
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
public StringBuffer() {
super(16);
}
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
public synchronized String toString() {
return new String(value, 0, count);
}
// 其他方法
}
不过,由于 StringBuffer 操作字符串的方法加了 synchronized 关键字进行了同步,主要是考虑到多线程环境下的安全问题,所以执行效率会比较低。
关于StringBuilder
与 StringBuffer 相比除了类名不同,方法没有加 synchronized,基本上完全一样。
实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer。
Java 编译器将字符串拼接操作(+)转换为了 StringBuilder 对象的 append 方法,然后再调用 StringBuilder 对象的 toString 方法返回拼接后的字符串。
来看一下 StringBuilder 的 toString 方法:
public String toString() {
return new String(value, 0, count);
}
value 是一个 char 类型的数组:
/**
* The value is used for character storage.
*/
char[] value;
在 StringBuilder 对象创建时,会为 value 分配一定的内存空间(初始容量 16),用于存储字符串。
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
随着字符串的拼接,value 数组的长度会不断增加,因此在 StringBuilder 对象的实现中,value 数组的长度是可以动态扩展的,就像ArrayList那样。
继续来看 StringBuilder 的 toString 方法:
public String toString() {
return new String(value, 0, count);
}
value 用于存储 StringBuilder 对象中包含的字符序列。count 是一个 int 类型的变量,表示字符序列的长度。toString() 方法会调用 new String(value, 0, count),使用 value 数组中从 0 开始的前 count 个元素创建一个新的字符串对象,并将其返回。
再来看一下 append 方法:
public StringBuilder append(String str) {
super.append(str);
return this;
}
实际上是调用了 AbstractStringBuilder 中的 append(String str) 方法。在 AbstractStringBuilder 中,append(String str) 方法会检查当前字符序列中的字符是否够用,如果不够用则会进行扩容,并将指定字符串追加到字符序列的末尾。
在 jdk 中 StringBuilder 类的实现中,采用 建造者模式 的思想。具体分析如下:
StringBuilder 类继承自 AbstractStringBuilder,而 AbstractStringBuilder实现了 Appendable接口。
AbstractStringBuilder 虽然是一个抽象类,但是它实现了 Appendable 接口中的各个 append() 方法,
因此在这里 Appendable 接口是一个抽象建造者,而 AbstractStringBuilder 是建造者,只是不能实例化。
对于 StringBuilder 类,它既充当了指挥者角色,同时充当了具体的建造者,建造方法的具体实现是由 AbstractStringBuilder 完成,
StringBuilder 继承了 AbstractStringBuilder。
建造者模式的4个角色
- Product(产品角色):一个具体的产品对象;
- Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类;
- ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件;
- Director(指挥者):构建一个使用Builder接口的对象。有两个作用:①隔离了客户与对象的生产过程;②负责控制产品对象的生产过程。