对Java中String、StringBuffer、StringBuilder的理解
摘要
极客时间里的Java核心技术系列,第5讲,记录一些笔记。
关键词:final,compact Strings,intrinsic,永久代,元数据区,堆
String
String类是我们最常用的一个类了,String类本身是 final 标记的。
底层涉及到的知识及优化也有很多。
JDK9中对 String 引入了 Compact Strings 的设计(完全用 byte[ ] 数组存储),对JVM底层优化的 Intrinsic 机制也进行了大幅度重写。
-
首先要知道它是 immutable 的,因为它底层存储数据的数组是 final 修饰的。
以前底层实现都是 char[ ] 数组,
JDK9改进成了 byte[ ] 数组 和 一个标识编码的 coder(默认是UTF16)。
因为:在 Java 中,char占2个字节,byte占1个字节。
拉丁系语言(反正不是中文)的字符,用不了太宽的char,就造成一定的空间浪费。 -
对于字符串常量的拼接: 比如 String str = “a” + “b” + “c”;
(可以通过 反编译 来查看验证)
在 JDK8 里,会被自动转化为 StringBuilder 来操作。
在 JDK9 里,提供了 StringConcatFactory 来作为入口优化操作。 -
对于字符串的缓存:
如果能够避免重复字符串的来回创建,那么就可以有效的降低 内存消耗 和 对象创建的开销。
在 Java6 中,在 String 类中提供了 intern() 方法(不推荐使用)。它实现的功能:如果缓存里有字符串,就返回缓存里的实例,如果没有,就把它加到缓存里。
被缓存的字符串存在于 PermGen(永久代)里,这里空间很小,并且基本只有 FullGC 这样的垃圾收集器来光顾。所以很容易 OOM 错误。
所以在后来,就被放到了 堆 中。
在 JDK8 中,MetaSpace(元数据区) 替代了 PermGen(永久代),默认缓存大小也在不断扩大。
可以用 -XX:+PrintStringTableStatistics 来查看。
也可以通过 -XX:StringTableSize=N 来调整大小。(一般不需要动,很少用到。) -
对字符串的优化:
字符串的一些基础操作会用,
JVM 内部的 Intrinsic 机制:利用 native 方式的 hard-coded(硬编码) 的逻辑,直接使用特定的CPU指令来操作 进行优化。
硬编码: 就是把代码写死,数据都直接写在方法里了,不会根据用户的输入而改变。
StringBuffer、StringBuilder
- 这2个类都继承了 AbstractStringBuilder 类。
在以前的版本也都是用 char[ ] 数组来存储的,在JDK9也都改成了用 byte[ ] 数组来存储。
默认给的 capacity 容量是: 初始化时的字符串长度 + 16(如果没有字符串,那么就是16)。 - 但是 StringBuffer 是线程安全的,
单纯就是给它的所有方法都加了 synchronized 关键字来实现,当然这也带来了一定的性能开销。 - 而 StringBuilder 是线程不安全,
但是它的功能和 StringBuffer 是一样一样的,是绝大部分情况下拼接字符串的首选。
参考链接:
https://www.jianshu.com/p/8a9b2c60e569
https://juejin.im/post/5c160420e51d4548271790ef
https://juejin.im/post/5c160420518825235a05301e
https://www.runoob.com/java/java-string.html