intern
9 static void test01(){ 10 String s1 = new String("1")+new String("23"); 11 s1.intern(); 12 String s2 = "123"; 13 System.out.println( s1 == s2);//true 14 } 15 16 static void test02(){ 17 String s1 = new String("1")+new String("23"); 18 String s2 = "123"; 19 s1.intern(); 20 System.out.println( s1 == s2); //false 21 }
s1.intern 在常量池植入引用,指向堆里面的"123",s2来的时候先看常量区,已经有了(虽然只是一个引用),直接给变量赋值
23 static void test03(){ 24 String s1 = new String("1")+new String("23"); 25 String s2 = "123"; 26 System.out.println( s1 == s2);//false 27 s1.intern(); 28 System.out.println( s1 == s2);//false 29 s1 = s1.intern(); 30 System.out.println( s1 == s2);//true 31 }
s1.intern试图在常量池植入"123",一看已经有了,返回常量池 “123”的引用,什么都不干,27行返回值没用所以28行还是false,29行用了给s1赋值,然后s1和s2就指向同一片了
如果常量池没有,那么会将堆中的字符串的引用放到常量池,注意是引用,然后返回该引用。
既然常量池已经在Java6之后放入了堆中,那么如果堆中已经创建过此字符串的对象了,那么就没有必要在常量池中再创建一个一毛一样的对象了,直接将其引用拷贝返回就好了,因为都是处于同一个区域Java堆中。
26 /** 27 * 第一行,StringBuilder只会在堆中创建一个对象,第二行调用intern方法后,会将堆中的引用放到到常量池中。 28 * 第三行发现常量池中已经有这个字符串的引用了,直接返回。 29 * 因此是同一个引用,返回的都是第一次创建的堆中字串的引用 30 */ 31 void test03() { 32 StringBuilder s1 = new StringBuilder("xyz"); 33 String s2 = s1.toString().intern(); 34 String s3 = "xyz"; 35 System.out.println(s2 == s3); // true 36 }
这个例子不能证明现在"xyz"真身是在常量池还是堆
48 /** 49 * new String之后使用 + 在Java中会进行编译优化,编译成字节码指令后,会将 + 优化成 先new Stringbuilder对象,然后调用append方法进行拼接。 50 * 因此这里s1最终创建的时候,xyzz字符串并没有在常量池创建,只是在堆中创建了,因为就如同上面的test03()一样,是new Stringbuilder操作。 51 * 所以在调用intern操作后,将其堆中的引用放入常量池并返回。 52 * 所以后面的结果都是true,因为至始至终都是堆中的一个对象。 53 */ 54 void test05() { 55 String s1 = new String("xyz") + new String("z"); 56 String s2 = s1.intern(); 57 String s3 = "xyzz"; 58 System.out.println(s1 == s2); // true 59 System.out.println(s1 == s3); // true 60 System.out.println(s2 == s3); // true 61 }
这个例子证明了"xyzz"真身在堆,因为s1一定在堆
56行,在常量池创建堆中真身的引用
72 /** 73 * s1指向的对象并没有改变 74 * s2指向常量区,s1指向堆,所以不一样 75 */ 76 void test07() { 77 String s1 = new String("xyz") + new String("z"); 78 String s2 = "xyzz"; 79 s1.intern(); 80 System.out.println(s1 == s2); // false 81 }
这个例子跟上一个例子key区别在于s2在定义的的时候,常量池还没有s1的引用。反证
83 /** 84 * s1.intern()之后,在常量区添加了堆中"xyzz"的引用,s2指向了这个常量池中"xyzz"对象 85 * 因此二者不相等 86 */ 87 void test08() { 88 String s1 = new String("xyz") + new String("z"); 89 s1.intern(); 90 String s2 = "xyzz"; 91 System.out.println(s1 == s2); // false 92 }
94 /** 95 * 第一个判断, 96 * s1.intern()之后,在常量区添加了堆中"xyzz"的引用 97 * s2也指向了常量池中这个引用,但是s1本身没有变,指的是堆中对象的引用,因此不相等 98 * <p> 99 * 第二个判断, 100 * s1 = s1.intern()以后,s1也指向了常量池中这个引用,因此相等 101 */ 102 void test09() { 103 String s1 = new String("xyz") + new String("z"); 104 s1.intern(); 105 String s2 = "xyzz"; 106 System.out.println(s1 == s2); // false 107 s1 = s1.intern(); 108 System.out.println(s1 == s2); // true 109 }
1 public class Test { 2 public static void main(String[] args) { 3 String str1 = new StringBuilder("计算机").append("软件").toString(); 4 String str2 = str1.intern(); 5 String str3 = new StringBuilder("ja").append("va").toString(); 6 String str4 = str3.intern(); 7 System.out.println(str1==str2); 8 System.out.println(str3==str4); 9 } 10 }
jdk1.8的输出答案是true和false。
jdk1.6的输出是两个false。
jdk1.7的intern方法不会在复制实例,只是在常量池中记录首次出现的实例引用。
因此str2指向的引用其实就是str1指向Java堆中StringBuilder创建的字符串实例。所以返回结果为true
但是java这个字符串常量在编译期就已经在方法区的常量池中了,不符合首次出现,所以str4指向的是常量池中的java字面量
所以返回结果为false。
https://www.cnblogs.com/r1-12king/p/15946648.html
26 /** 27 * 第一行,StringBuilder只会在堆中创建一个对象,第二行调用intern方法后,会将堆中的引用放到到常量池中。 28 * 第三行发现常量池中已经有这个字符串的引用了,直接返回。 29 * 因此是同一个引用,返回的都是第一次创建的堆中字串的引用 30 */ 31 void test03() { 32 StringBuilder s1 = new StringBuilder("xyz"); 33 String s2 = s1.toString().intern(); 34 String s3 = "xyz"; 35 System.out.println(s2 == s3); // true 36 }
实践:1.8
// String row1 = ("xxxxx"); // String row2 = new String("xxxxx"); // row2 = row2.intern(); // System.out.println(row1 == row2); //true // String row1 = new String("xxxxx"); // String row2 = new String("xxxxx"); // row2 = row2.intern(); // System.out.println(row1 == row2); //false // String row1 = new String("xxxxx"); // String row2 = new String("xxxxx"); // System.out.println(row1 == row2); // false // row2 = row2.intern(); // row1 = row1.intern(); // System.out.println(row1 == row2); // true
intern的缺点:
这个功能为String提供了字符串池,我们可以使用它来优化内存。 但是,这有一个缺点:在OpenJDK中,String.intern()是本地方法,它实际上调用了JVM的相关方法来实现该功能。这样实现的原因是,当VM和JDK代码必须就特定String对象的标识达成一致时,String interning就必须是JDK-VM接口的一部分。
这样的实现意味着:
- 您需要在每个intern调用使用JDK-JVM接口,这会浪费CPU。
- 性能受本地HashTable实现的影响,可能落后于高性能Java版本,特别是在并发访问的情况下。
- 由于Java Strings是来自VM的引用,因此它们成为GC rootset的一部分。 在许多情况下,这需要在GC停顿期间执行额外的工作。
https://www.cnblogs.com/helloworld2048/p/java_string_intern.html