video link : www.bilibili.com/video/BV1PJ…
1、 String的基本特性
String声明为final,不可继承
String实现了Serializable接口,支持序列化;实现了Comparable接口,可比较大小
String在java8及之前内部定义了final char[] value用于存储字符串数据;java9改为byte[]
因为对于英文字符来说,用一个byte就能存储
而在java8及之前的char,使用了两个byte,浪费了空间
java9中,使用byte加编码标记
String代表不可变的字符序列
当对字符串重新赋值、进行连接操作、replace()方法修改时,需要重新指定内存区域赋值,不能使用原有的value
通过字面量的方式(区别于new)给一个字符串赋值时,此时的字符串值声明在字符串常量池中
字符串常量池中不会存储相同内容的字符串
String pool是一个固定大小的HashTable,默认值是1009。如果String非常多,会造成严重hash冲突,从而导致链表很长,而链表长了之后直接会导致调用intern()时性能大幅下降
使用-XX:StringTableSize可设置该参数
java6中默认1009且设置没有要求
java7中默认是60013且设置没有要求
java8中默认60013且可设置的最小值为1009
2、String的内存分配
String常量池主要使用方法有两种:
- 直接使用双引号声明出来的String对象会直接存储在常量池中
- 如果不是使用双引号声明的String对象,可使用intern()
字符串常量池位于堆中
3、字符串拼接操作
- 常量与常量的拼接结果在常量池,编译器进行该优化
- 常量池中不会存在相同内容的常量
- 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
- 如果拼接的结果调用intern(),则主动将常量池中还没有的字符串放入池中,并返回此对象地址
/**
如下代码执行细节:
StringBuilder s = new StringBuilder();
s.append("a");
s.append("b");
s.toString();
*/
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;
/**
对于如下final修饰的字符串,仍然使用编译期优化
PS: 能用final的场景,尽量用final
*/
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String src = "";
for (int i = 0; i < 1000000; i++) {
// 每次循环都会创建StringBuilder、String
// 创建的对象多,进行GC时,消耗也更大
src += "a";
}
StringBuilder src = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
// 只创建一个对象
// 改进:如果能大致确定最终字符串长度,可初始化StringBuilder容量,减少扩容次数
src.append("a");
}
4、intern()的使用
jdk1.6中:
- 如果串池中有,则不会放入,返回已有的串池中的对象的地址(
s.intern()和String ss = s.intern()不同) - 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
jdk1.7起:
- 如果串池中有,则不会放入,返回已有的串池中的对象的地址(
s.intern()和String ss = s.intern()不同) - 如果没有,会把此对象的引用地址复制一份,放入串池,并返回串池中的对象地址
当程序中有大量重复的字符串时,调用intern()能够节省内存
// for(1...1m): 会产生1m个String对象需要维护
String s1 = new String("test");
// for(1...1m): 调用intern()后,产生的很多String,会很快销毁无需维护
String s2 = new String("test").intern()