简介
intern用来返回常量池中的某字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中加入该对象,然后 返回引用。在jdk1.7之前,字符串常量存储在方法区的PermGen Space。在jdk1.7之后,字符串常量重新被移到了堆中。
String被设计成final的原因
字符串常量池的需要。字符串常量池的诞生是为了提升效率和减少内存分配。可以说我们编程有百分之八十的时间在处理字符串,而处理的字符串中有很大概率会出现重复的情况。正因为String的不可变性,常量池很容易被管理和优化。
安全性考虑。正因为使用字符串的场景如此之多,所以设计成不可变可以有效的防止字符串被有意或者无意的篡改。从java源码中String的设计中我们不难发现,该类被final修饰,同时所有的属性都被final修饰,在源码中也未暴露任何成员变量的修改方法。(当然如果我们想,通过反射或者Unsafe直接操作内存的手段也可以实现对所谓不可变String的修改)。
作为HashMap、HashTable等hash型数据key的必要。因为不可变的设计,jvm底层很容易在缓存String对象的时候缓存其hashcode,这样在执行效率上会大大提升。
实例说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| String s1 = new String("aaa"); String s2 = "aaa"; System.out.println(s1 == s2);
s1 = new String("bbb").intern(); s2 = "bbb"; System.out.println(s1 == s2);
s1 = "ccc"; s2 = "ccc"; System.out.println(s1 == s2);
s1 = new String("ddd").intern(); s2 = new String("ddd").intern(); System.out.println(s1 == s2);
s1 = "ab" + "cd"; s2 = "abcd"; System.out.println(s1 == s2);
String temp = "hh"; s1 = "a" + temp;
s2 = "ahh"; System.out.println(s1 == s2);
temp = "hh".intern(); s1 = "a" + temp; s2 = "ahh"; System.out.println(s1 == s2);
temp = "hh".intern(); s1 = ("a" + temp).intern(); s2 = "ahh"; System.out.println(s1 == s2);
s1 = new String("1"); s1.intern(); s2 = "1"; System.out.println(s1 == s2);
String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);
s3 = new String("2") + new String("2"); s4 = "22"; s3.intern(); System.out.println(s3 == s4);
|
具体的字节码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
String s = "11";
String s1 = new String("11");
String s2 = new String("1") + new String("1"); s2.intern();
String s3 = "a" + "b" + "c";
String s1 = new StringBuilder("why").append("true").toString(); System.out.println(s1 == s1.intern());
|
字符串拼接优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| String a = "1"; for (int i=0; i<10; i++) { a += i; } 0: ldc #16; 2: astore_1 3: iconst_0 4: istore_2 --- 循环开始 5: goto 30 8: new #18; 11: dup 12: aload_1 13: invokestatic #20; 16: invokespecial #26; 19: iload_2 20: invokevirtual #29; 23: invokevirtual #33; 26: astore_1 27: iinc 2, 1 ---- 计数加1 30: iload_2 31: bipush 10 33: if_icmplt 8
|
可知,真正的性能瓶颈在于每次循环都建了一个StringBuilder对象
所以我们优化一下 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| StringBuilder sb = new StringBuilder("1"); for (int i=0; i<10; i++) { sb.append("1"); } 对应的字节码为: 0: new #16; 3: dup 4: ldc #18; 6: invokespecial #20; 9: astore_1 10: iconst_0 11: istore_2 12: goto 25 15: aload_1 16: ldc #18; 18: invokevirtual #23; 21: pop 22: iinc 2, 1 25: iload_2 26: bipush 10 28: if_icmplt 15
|
参考资料
Java-String.intern的深入研究