Java核心 -- 字符串
String
- String是Java语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑,是典型的Immutable类
- String是Immutable类的典型实现,原生的保证了基础线程安全,因为无法对它内部数据进行任何修改
- 被声明为final class,由于String的不可变性,类似拼接、裁剪字符串等动作,都会产生新的String对象
- 由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响
StringBuffer
- StringBuffer是为了解决拼接产生太多中间对象的问题而提供的一个类
- StringBuffer本质是一个线程安全的可修改字符串序列,保证了线程安全,但也带来了额外的性能开销
- StringBuffer的线程安全是通过把各种修改数据的方法都加上synchronized关键字实现的
- 这种方式非常适合常见的线程安全类的实现,不必纠结于synchronized的性能
- 过早的优化是万恶之源,可靠性、正确性和代码可读性才是大多数应用开发的首要考虑因素
StringBuilder
- StringBuilder在能力上和StringBuffer没有本质区别,但去掉了线程安全的部分,有效减小了开销
- StringBuffer和StringBuilder底层都是利用可修改的数组(Java 8为char[],Java 9为byte[]),都继承AbstractStringBuilder
- 区别仅在于最终的方法是否有synchronized关键字
- 内部数组初始大小为初始字符串长度+16
- 如果确定拼接会发生多次,并且是可预计的,最好指定合适的初始大小,避免多次扩容的开销(arraycopy)
字符串拼接
1 | public class StringConcat { |
先用javac编译,再用javap反编译
Java 8
1 | public static void main(java.lang.String[]); |
- “aa” + “bb” + “cc” + “dd”会被当成常量”aabbccdd”
- 字符串的拼接操作会自动被javac转换成StringBuilder操作
Java 11
1 | public static void main(java.lang.String[]); |
- “aa” + “bb” + “cc” + “dd”同样会被当成常量”aabbccdd”
- Java 11为了更加统一字符串操作优化,提供了StringConcatFactory,作为一个统一的入口
- javac自动生成的代码,未必是最优的,但针对普通场景已经足够了
字符串缓存
- 把常见应用进行Heap Dump,然后分析对象组成,大约25%的对象是字符串,并且其中约50%是重复的
- 如果能避免创建重复字符串,可以有效降低内存消耗和对象创建开销
- String在Java 6提供了intern(),目的是提示JVM把相应的字符串缓存起来
- 创建String对象并且调用intern(),如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来
- 被缓存的字符串会存储在PermGen(永久代),PermGen的空间非常有限,只有FullGC会处理PermGen
- 所以,如果使用不当,会触发OOM
- 另外,intern()是一种显式地排重机制,但这也是一种_代码污染_
- 在后续的Java版本中,字符串缓存被放置在堆中,极大的避免了PermGen占满的问题
- 在Java 8中被MetaSpace(元数据区)取代了
- 默认缓存大小也在不断地扩大,可以通过
-XX:+PrintStringTableStatistics
查看- 也可以通过
-XX:StringTableSize=N
调整大小,但绝大部分情况下不需要调整
- 也可以通过
- 在Oracle JDK 8u20出现了G1 GC的字符串排重,通过将相同数据的字符串指向同一份数据来实现的
- 这是JVM底层的改变,并不需要Java类库做修改
- 该功能目前是默认关闭的,启动参数
-XX:+UseStringDeduplication
1 | $ java -XX:+PrintStringTableStatistics -version |
Intrinsic
- 在运行时,字符串的一些基础操作会直接利用JVM内部的Intrinsic机制
- 往往运行的是特殊优化的本地代码,而不是Java代码生成的字节码
- Intrinsic:是一种利用native方式hard-coded的逻辑,算是一种特殊的内联,很多优化还需要使用特定的CPU指令
- 通过
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
查看
1 | $ java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -version |
字符串压缩
- 在Java的历史版本中,使用了char[]来存储数据
- 但char占用2个byte,而拉丁语系语言的字符,不需要太宽的char,这会造成一定的浪费
- 在Java 9中引入了Compact Strings的设计
- 将存储方式从char[]数组改变为一个byte[]加上一个标识编码的coder
- 并且将相关字符串操作类都进行了修改,所有相关的Intrinsic都进行了重写,保证没有任何性能损失
- 该特性对绝大部分应用来说是透明的
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.