52.String内存结构位置和String拼接操作(面试)
1.String
内存结构位置
1.Java
中的8
种基本数据类型以及String
,为了使他们在运行过程中更快,更节省内存,都提供了常量池的概念。
2.8
种基本数据类型是由系统协调的,String
类型的常量池主要的使用方法有两种:直接使用双引号的方式声明字符串常量;使用String
提供的intern()
方法。
3.JDK6
及以前,字符串常量池放在永久代。JDK7
及以后,字符串常量池放在堆区。
为什么StringTable
要从永久代放到堆区?
2.String
的拼接操作
1.常量与常量的拼接结果放在常量池,原理是编译期优化
2.常量池中不会存放相同内容的常量
3.拼接操作,只要其中一个是变量,结果就放在堆中。底层使用的是StringBuilder
4.如果拼接后的结果调用intern()
方法,则会先去常量池中检查存不存在该字符串,如果不存在,就会将该字符串放入常量池中,否则直接返回常量池中已经存在的字符串的地址
例子:
/** * 字符串拼接操作 */ public class StringTest5 { @Test public void test1(){ String s1 = "a" + "b" + "c";//常量与常量的拼接操作,结果放在常量池。编译期优化:等同于"abc" String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2 /* * 在编译阶段,上面的代码就已经被替换成如下的代码了 * String s1 = "abc"; * String s2 = "abc" */ System.out.println(s1 == s2); //true System.out.println(s1.equals(s2)); //true } @Test public void test2(){ String s1 = "javaEE"; String s2 = "hadoop"; String s3 = "javaEEhadoop"; String s4 = "javaEE" + "hadoop";//编译期优化 //如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop String s5 = s1 + "hadoop"; String s6 = "javaEE" + s2; String s7 = s1 + s2; System.out.println(s3 == s4);//true System.out.println(s3 == s5);//false System.out.println(s3 == s6);//false System.out.println(s3 == s7);//false System.out.println(s5 == s6);//false System.out.println(s5 == s7);//false System.out.println(s6 == s7);//false //intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址; //如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。 String s8 = s6.intern(); System.out.println(s3 == s8);//true } }
5.体会字符串拼接操作,底层StringBuilder
的使用
第3
点讲过,字符串拼接操作,只要其中一个是变量,结果就放在堆中。底层使用的是StringBuilder
。
具体的细节是:先new
一个StringBuilder
出来,然后调用append
方法,将字符串加入new
出来的StringBuilder
中,最后调用toString
方法生成最终的字符串。(例如代码中的s1 + s2
操作)
6.字符串拼接操作不一定使用的是StringBuilder
。如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder
的方式。
例如代码中的s1
和s2
使用final
修饰,实际上它们已经成为了字符串常量。如果将它们进行拼接,相当于是常量与常量的拼接,结果会直接放在常量池中。
final String s1 = "a"; // final 修饰,s1实际上已经成为了字符串常量 final String s2 = "b"; // final 修饰,s1实际上已经成为了字符串常量 String s4 = s1 + s2; // 这里相当于是常量和常量的拼接,所以结果是放在常量池中
例子:
/** * 字符串拼接操作 */ public class StringTest5 { @Test public void test3(){ String s1 = "a"; String s2 = "b"; String s3 = "ab"; /* 如下的s1 + s2 的执行细节:(变量s是我临时定义的,底层使用的是StringBuilder,但不一定是s) ① StringBuilder s = new StringBuilder(); ② s.append("a") ③ s.append("b") ④ s.toString() --> 约等于 new String("ab") 补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer */ String s4 = s1 + s2;// System.out.println(s3 == s4);//false } /* 1. 字符串拼接操作不一定使用的是StringBuilder! 如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。 2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。 */ @Test public void test4(){ final String s1 = "a"; // final 修饰,s1实际上已经成为了字符串常量 final String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; // 这里相当于是常量和常量的拼接,所以结果是放在常量池中 System.out.println(s3 == s4);//true } //练习: @Test public void test5(){ String s1 = "javaEEhadoop"; String s2 = "javaEE"; String s3 = s2 + "hadoop"; System.out.println(s1 == s3);//false final String s4 = "javaEE";//s4:常量 String s5 = s4 + "hadoop"; System.out.println(s1 == s5);//true } }