Java性能 -- 字符串
实现
- 在Java 6以及之前的版本中,String对象是对char数组进行了封装实现的对象
- 主要四个成员变量:char数组、偏移量offset、字符数量count、哈希值hash
- String对象通过offset和count两个属性来定位char数组,获取字符串
- 这样可以高效快速地共享数组对象,同时节省内存空间,但也有可能会导致内存泄露
- Java 7/8,String类中不再有offset和count两个变量
- 这样String对象占用的内存稍微少了一点
- 另外,String.substring不再共享char[],从而解决了使用该方法可能导致的内存泄露问题
- 从Java 9开始,将char[]修改为byte[],同时维护了一个新的属性coder,它是一个编码格式的标识
- 一个char字符占用16位,2个字节,用一个char去存储单字节编码的字符会非常浪费
- Java 9的String类为了节约内存空间,使用了占用8位,1个字节的byte数组来存放字符串
- coder的作用:使用length()或者indexOf()
- coder有两个默认值:0代表LATIN1,1代表UTF16
不可变性
- String是final类,char[]也是被private final修饰,这样实现了String对象的不可变性
- 好处
- 保证了String对象的安全性
- 保证了hash属性不会频繁变更,确保了唯一性,使得HashMap等容器得以实现相应的Key-Value缓存功能
- 可以实现字符串常量池
创建字符串对象
字面值
String s = "abc";
- JVM首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建
- 这种方式可以减少同一个值的字符串对象被重复创建,节约内存
new
String s = new String("abc");
- 首先,在编译类文件时,
"abc"
常量字符串将会放入到常量结构中,在类加载时,"abc"
将会在常量池中创建 - 其次,在调用new时,JVM会调用String的构造函数,同时将引用常量池中的
"abc"
字符串,在堆内存中创建一个String对象 - 最后,s将引用刚刚创建的String对象
优化
拼接
String s = "ab" + "cd" + "ef";
- 编译器优化:
String s = "ab" + "cd" + "ef";
- 编译器优化:
String s = "abcdef"; for ( int i = 0; i < 1000 ; i++ ) { str = str + i; }
- 编译器优化:采用StringBuilder进行字符串拼接
- 但每次循环都会生成一个新的StringBuilder实例,同样也会降低系统性能
- 因此做字符串拼接时,最好显式使用StringBuilder
intern
- 在字符串常量中,默认会将对象放入到常量池中
- 在字符串变量中,对象是会创建在堆内存中,同时也会在常量池中创建一个字符串对象
- 并将常量池中字符串对象的char[]引用赋值给堆内存对象中,并返回堆内存对象引用
- 如果调用intern方法,会去查看字符串常量池中是否有等于该对象的字符串
- 如果没有,就在常量池中新增该对象,并返回该对象引用
- 如果有,就返回常量池中的字符串对象引用
- 而堆内存中原有的对象由于没有引用指向它,将会被回收掉
- 常量池的实现类似于一个HashTable,存量的数据越多,遍历的时间复杂度会增加
1 | // 1. 在类加载时,在常量池中创建一个字符串对象“abc” |
1 | String s1 = "abc"; // 常量池 |
split
- split使用正则表达式实现了强大的分割功能,但_正在表达式的性能非常不稳定_
- 使用不恰当会引起回溯问题,导致CPU居高不下
- 所以应该谨慎使用split方法,可以考虑用indexOf来替代
参考资料
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.