字符串深入解析

之所以深入研究,是因为在看《深入理解java虚拟机》第三版时,看到对字符串常量池的相关解析,对其中的部分不理解,所以亲自试验,毕竟实践是检验真理的唯一标准

第一种情况

String str1 = new StringBuilder("计算机").append("软件").toString();
String str3 = new StringBuilder("计算机").append("软件").toString();

System.out.println("str1的地址:" + System.identityHashCode(str1));
System.out.println("str1.intern()的地址:" + System.identityHashCode(str1.intern()));
System.out.println("str3的内存地址:" + System.identityHashCode(str3));
System.out.println("str3.intern的内存地址:" + System.identityHashCode(str3.intern()));
运行结果:
jdk6:
str1的地址:714682869
str1.intern()的地址:798941612
str3的内存地址:1743911840
str3.intern的内存地址:798941612
jdk7:
str1的地址:1770930187
str1.intern()的地址:1770930187
str3的内存地址:2047789136
str3.intern的内存地址:1770930187
jdk8:
str1的地址:943010986
str1.intern()的地址:943010986
str3的内存地址:1807837413
str3.intern的内存地址:943010986
解析:
第一行结果解析:由于是不同对象,所以内存地址不一样,所以返回为false
第二行结果解析:运行str1.intern方法和str1的比较在不同jdk下出现了不同的结果
jdk6:由于jdk6的虚拟机里面,方法区实现是永久代,常量池存在于永久代中,方法区和堆区是不同的两个区
	 执行intern方法,若字符串常量池中没有此字符串,会在常量池中创建一个此字符串的副本,
	 因此副本地址与堆中的地址不一样
jdk7:由于jdk7的虚拟机,将常量池移动到了堆区中,执行intern方法,若字符串常量池中没有此字符串,
	 会将字符串对象的引用放到常量池中,则该常量池中的地址和堆中的地址是同一个地址
jdk8:由于jdk8的虚拟机,将常量池移动到了元空间,执行intern方法,若字符串常量池中包含
	 与当前对象相当的字符串,将返回常量池中的字符串;若不存在,将对象引用放入常量池,
	 则常量池中的地址和堆中的地址为同一个地址

第二种情况

String str4 = "计算机软件-1";
String str5 = "计算机软件-1";
System.out.println("str4的地址:" + System.identityHashCode(str4));
System.out.println("str4.intern()的地址:" + System.identityHashCode(str4.intern()));
System.out.println("str5的内存地址:" + System.identityHashCode(str5));
System.out.println("str5.intern()的内存地址:" + System.identityHashCode(str5.intern()));
运行结果:
jdk6:
str4的地址:1743911840
str4.intern()的地址:1743911840
str5的内存地址:1743911840
str5.intern()的内存地址:1743911840
jdk7:
str4的地址:687006504
str4.intern()的地址:687006504
str5的内存地址:687006504
str5.intern()的内存地址:687006504
jdk8:
str4的地址:2066940133
str4.intern()的地址:2066940133
str5的内存地址:2066940133
str5.intern()的内存地址:2066940133
解析:
上述情况只会在常量池中创建一个字符串,所以无论有没有执行intern方法,地址都为常量池中的地址

第三种情况

String str6 = new String("计算机软件-2");
String str7 = new String("计算机软件-2");
System.out.println("str6的地址:" + System.identityHashCode(str6));
System.out.println("str6.intern()的地址:" + System.identityHashCode(str6.intern()));
System.out.println("str7的内存地址:" + System.identityHashCode(str7));
System.out.println("str7.intern()的内存地址:" + System.identityHashCode(str7.intern()));
System.out.println(str6.hashCode() + "===" + str7.hashCode());
运行结果:
jdk6:
str6的地址:1069480624
str6.intern()的地址:322722178
str7的内存地址:1595436971
str7.intern()的内存地址:322722178
498723120===498723120
jdk7:
str6的地址:473031341
str6.intern()的地址:253286993
str7的内存地址:681902997
str7.intern()的内存地址:253286993
498723120===498723120
jdk8:
str6的地址:48612937
str6.intern()的地址:325333723
str7的内存地址:1937962514
str7.intern()的内存地址:325333723
498723120===498723120
解析:
此种情况,会在常量池中创建一个字符串,然后在堆中创建两个对象,intern()返回的永远是常量池中的地址,
所以str6和str7在常量池中的地址一样,但是在堆中是不同的地址

Object的hashCode()默认是返回内存地址的,但是hashCode()可以重写,所以hashCode()不能代表
内存地址的不同

System.identityHashCode(Object)方法可以返回对象的内存地址,不管该对象的类是否重写了hashCode()方法

第四种情况

String str2 = new StringBuilder("ja").append("va").toString();
System.out.println("str2的内存地址:" + System.identityHashCode(str2));
System.out.println("str2.intern的内存地址:" + System.identityHashCode(str2.intern()));
运行结果:
jdk6:
str2的内存地址:1028355155
str2.intern的内存地址:616699029
jdk7:
str2的内存地址:2047789136
str2.intern的内存地址:1619081930
jdk8:
str2的内存地址:274064559
str2.intern的内存地址:1018081122
解析:
此种情况,会在常量池中创建“ja”,"va"的两个字符串,然后toString()会产生
new String()的对象,在堆中会有一个对象,按理说执行intern方法应该和第一
种情况一致,但是事实情况却不是,造成这种原因的问题是“java”这个字符串由于
jvm的特性,在JVM加载的时候已经将"java"字符串放入了常量池,所以执行intern
返回的是常量池中已经存在的

参考文章:
字符串常量池深入解析
String:字符串常量池
深入解析String#intern
JDK1.8版本java字符串常量池里存的是String对象还是引用?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章