String,StringBuffer,StringBuilder 有什么区别? 这个问题在面试中经常碰到, 今天主要讲解一下如何理解 Java 中的 String,StringBuffer,StringBuilder.
典型回答
String 是 Java 语言非常基础和重要的类, 提供了构造和管理字符串的各种基本逻辑. 它是典型的不可变类( Immutable ), 被声明成为 final class, 所有属性也都是 final 的. 也由于它的不可变性, 类似拼接, 裁剪字符串等动作, 都会产生新的 String 对象. 由于字符串操作的普遍性, 所以相关操作的效率往往对应用性能有明显影响.
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类, 我们可以用 append 或者 add 方法, 把字符串添加到已有序列的末尾或者指定位置. StringBuffer 本质是一个线程安全的可修改字符序列, 它保证了线程安全, 也随之带来了额外的性能开销, 所以除非有线程安全的需要, 不然还是推荐使用它的后继者, 也就是 StringBuilder.
StringBuilder 是 Java 1.5 中新增的, 在能力上和 StringBuffer 没有本质区别, 但是它去掉了线程安全的部分, 有效减小了开销, 是绝大部分情况下进行字符串拼接的首选.
考点分析
几乎所有的应用开发都离不开操作字符串, 理解字符串的设计和实现以及相关工具如拼接类的使用, 对写出高质量代码是非常有帮助的. 关于这个问题, 前面的回答是一个通常的概要性回答, 至少你要知道 String 是 Immutable 的, 字符串操作不当可能会产生大量临时字符串, 以及线程安全方面的区别.
如果继续深入, 面试官可以从各种不同的角度考察, 比如可以:
通过 String 和相关类, 考察基本的线程安全设计与实现, 各种基础编程实践.
考察 JVM 对象缓存机制的理解以及如何良好地使用.
考察 JVM 优化 Java 代码的一些技巧.
String 相关类的演进, 比如 Java 9 中实现的巨大变化.
...
知识扩展
字符串设计和实现考量
String 是 Immutable 类的典型实现, 原生的保证了基础线程安全, 因为你无法对它内部数据进行任何修改, 这种便利甚至体现在拷贝构造函数中, 由于不可变, Immutable 对象在拷贝时不需要额外复制数据.
我们再来看看 StringBuffer 实现的一些细节, 它的线程安全是通过把各种修改数据的方法都加上 synchronized 关键字实现的, 非常直白. 其实, 这种简单粗暴的实现方式, 非常适合我们常见的线程安全类实现, 不必纠结于 synchronized 性能之类的, 有人说 "过早优化是万恶之源", 考虑可靠性, 正确性和代码可读性才是大多数应用开发最重要的因素.
为了实现修改字符序列的目的, StringBuffer 和 StringBuilder 底层都是利用可修改的 (char,JDK 9 以后是 byte) 数组, 二者都继承了 AbstractStringBuilder, 里面包含了基本操作, 区别仅在于最终的方法是否加了 synchronized.
在具体的代码书写中, 应该如何选择呢?
在没有线程安全问题的情况下, 全部拼接操作是应该都用 StringBuilder 实现吗? 毕竟这样书写的代码, 还是要多敲很多字的, 可读性也不理想, 下面的对比非常明显.
- String strByBuilder = new
- StringBuilder().append("aa").append("bb").append("cc").append
- ("dd").toString();
- String strByConcat = "aa" + "bb" + "cc" + "dd";
其实, 在通常情况下, 没有必要过于担心, 要相信 Java 还是非常智能的.
我们来做个实验, 把下面一段代码, 利用不同版本的 JDK 编译, 然后再反编译, 例如:
- public class StringConcat {
- public static void main(String[] args) {
- String myStr = "aa" + "bb" + "cc" + "dd";
- System.out.println("My String:" + myStr);
- }
- }
先编译再反编译
- ${
- JAVA_HOME
- }/bin/javac StringConcat.java
- ${
- JAVA_HOME
- }/bin/javap -v StringConcat.class
JDK 8 的输出片段是:
- 0: ldc #2 // String hellojava!
- 2: astore_1
- 3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
- 6: new #4 // class java/lang/StringBuilder
- 9: dup
- 10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
- 13: ldc #6 // String Concat String:
- 15: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 18: aload_1
- 19: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 22: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
- 25: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 28: return
你可以看到, 在 JDK 8 中, 字符串拼接操作会自动被 javac 转换为 StringBuilder 操作, 而在 JDK 9 里面则是因为 Java 9 为了更加统一字符串操作优化, 提供了 StringConcatFactory, 作为一个统一的入口. javac 自动生成的代码, 虽然未必是最优化的, 但普通场景也足够了, 你可以酌情选择.
参考自极客时间: Java 核心技术 36 讲
感谢原作者: 杨晓峰老师
来源: http://www.jianshu.com/p/5b1eab7e2b65