字符串常量
String是不可变的字符序列,体现为以下三种情况
- 当字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
- 当对现有字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
- 当调用String的replace方法修改指定字符串时,也需要重新指定内存区域赋值,不能使用原有value进行赋值
通过字面量的方式给一个字符串赋值,字符串值声明在了字符串常量池中,字符串常量池中不会存储相同的字符串的
1 | String name = "张三"; |
字符串常量池的底层结构
String的String Pool是一个固定大小的Hashtable,如果放入String Pool的String非常多,会造成Hash冲突,从而导致链表很长,链表长会造成调用String.intern时性能大幅下降
使用-XX:StringTableSize可以设置StringTable的长度
默认为60013,可以设置的最小值是1009
1
2
3 jinfo -flag StringTableSize 84315
-XX:StringTableSize=60013
字符串常量池的存储位置
字符串常量到底存在哪呢?在jdk6的时候将字符串常量池存放在永久代,但是由于永久代的回收效率很低,只有在full gc的时候才会触发,而开发中经常会用到大量的字符串,如果回收效率低,会导致永久代代内存不足,到了jdk7中将字符串常量池存储在堆中,这样可以保证字符串常量可以及时回收
字符串拼接
如果是常量与常量拼接,拼接的结果在常量池(这个是在编译期完成的)
1
2
3
4
5
6
7// java文件中
String s1 = "z"+"h";
String s2 = "zh";
// 编译成class文件变为
String s1 = "zh";
String s2 = "zh";只要其中有一个是变量,结果就在堆中,底层使用的是StringBuilder
1
2
3
4
5
6
7
8
9
10
11// java文件中
String s1 = "z";
String s2 = "h";
String s3 = "zh";
String s4 = s1 + s2;
// 编译成class文件变为
String s1 = "z";
String s2 = "h";
String s3 = "zh";
(new StringBuilder()).append(s1).append(s2).toString();
StringBuilder的toString方法,这个方法生成的字符串不会放到字符串常量池中
1 | public String toString() { |
如果拼接的结果调用intern()方法,判断常量池中是否存在该结果所对应的的字符串值,如果存在,则返回常量池中该字符串值的地址,如果常量池中不存在,则在常量池中加载一份,并返回此对象的地址
如果有两个字符串s和t,则s.equals(t)为true则s.intern() == t.intern(),反之亦然
intern确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度
1 | // 执行完之后常量池中并没有11 |
由于从jdk7开始常量池是在堆中的存放的,所以在使用intern()方法时,如果常量池中有的话,会直接返回常量池中已有的该字符串的地址;如果常量池中没有,则会把对象的引用地址复制一份,放入到常量池中,并返回常量池中该字符串的引用地址,而这个引用地址其实就是刚放进去的字符串引用地址