0%

字符串常量

字符串常量

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

//字节码
0 new #41 <java/lang/String>
3 dup
4 aload_0
5 getfield #42 <java/lang/StringBuilder.value>
8 iconst_0
9 aload_0
10 getfield #43 <java/lang/StringBuilder.count>
13 invokespecial #44 <java/lang/String.<init>>
16 areturn
  • 如果拼接的结果调用intern()方法,判断常量池中是否存在该结果所对应的的字符串值,如果存在,则返回常量池中该字符串值的地址,如果常量池中不存在,则在常量池中加载一份,并返回此对象的地址

    如果有两个字符串s和t,则s.equals(t)为true则s.intern() == t.intern(),反之亦然

    intern确保字符串在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度

1
2
3
4
5
6
7
8
// 执行完之后常量池中并没有11
String s1 = new String("1") + new String("1");
// 由于常量池中没有11,所以常量池指向了s1的内存地址
s1.intern();
// 这里是从常量池中取的
String s2 = "11";
// 所以两者一致
System.out.println(s1 == s2);//true

由于从jdk7开始常量池是在堆中的存放的,所以在使用intern()方法时,如果常量池中有的话,会直接返回常量池中已有的该字符串的地址;如果常量池中没有,则会把对象的引用地址复制一份,放入到常量池中,并返回常量池中该字符串的引用地址,而这个引用地址其实就是刚放进去的字符串引用地址