在工作中使用String的地方很多,但是有时候会产生一点疑问,那就是项目中用到了这么多String,会不会对内存造成一定的压力,会不会降低程序的性能,字符串的值都存放到哪里去了?所以就深入了解了一下。
然而网上搜索到的内容很多很全面,但这意味着很杂,难以把握,不利于理解和吸收,基于以上原因,我查阅了众多技术博客,总结了一下基于JDK1.8.0_211的测试结果和理论。
先来理解一下:【堆、栈、字符串常量池】 ,这里只聊涉及到字符串常量池的地方。
JDK1.8之前的不管,主要是JDK1.8+之后的版本,因为这也是我目前在用的版本。本文的所有结论,方法作用,jvm的内存,都是指的JDK1.8+的版本。
在Java的JVM的内存中分为3个区:堆、栈和方法区。
栈区
栈中分配的是基本数据类型和自定义对象的引用。
堆区
堆中分配的是对象,也就是new出来的东西, 被所有线程共享。其中包含了本篇文章的重点字符串常量池。
方法区(此篇文章不涉及方法区)
又叫静态区,存放的是类信息和static变量、常量,被所有线程共享。
结论
一、只在常量池创建常量
String str = "A";
我们平时在方法中,会声明一个String类型的变量,比如上面这种。
那么,str对象就会被存放到栈中,字面量 “A” 就会被创建到堆中的字符串常量池中。
二、在堆上创建对象,在常量池创建常量
String str = new String("A");
这种方式,会在堆中创建一个对象str,也会在常量池中创建一个常量"A"。
三、只在堆上创建对象
String str = new String("A") + new String("B");
这种方式,会在堆中创建三个对象:两个匿名对象,分别代表“A”和“B”;一个str对象,代表“AB”。
也会在常量池中创建两个常量,“A”和“B”。但是不会创建常量“AB”。(图片有所简化)
四、在堆上创建对象,在常量池上创建引用
String str = new String("A") + new String("B");
str.intern();
可以发现,这种方式,是在第三点的基础上,加了一行代码。
这行代码的作用是:如果常量池里是空的(不存在常量“AB”),就在常量池里面创建一个引用(指向堆中的对象str);非空,不做任何操作。返回值是常量池里的内容。
注意要点:
1.常量池中有两种类型的存放,第一种存放的是常量;第二种存放的是堆中对象的引用(指针)。
2.intern()方法的使用:如果常量池里是空的,就在常量池里面创建一个引用(指向堆中的对象);非空,不做任何操作。返回值是常量池里的内容。
3.堆中可以有任意个相同的字符串,但常量池中只能有一个 常量或引用。
举例:【当常量池中存在常量“AB”,堆中的对象(值为“AB”)调用intern()方法,会检测到常量池中已经存在“AB”,就不会再在常量池中创建引用,而是会返回常量“AB”。同理,如果常量池中已经存在对象(值为“AB”)的引用,那么就不会再创建常量“AB”,而是返回堆内存中对象的引用。】
4." " 和intern() 其实很像。区别就是在常量池为空时,“ ”是把值加进去,intern()是把引用加进去。
验证
案例一
String str1 = "A"; //创建:常量池常量“A”
String str2 = "A"; //创建:常量池常量“A”
System.out.println(str1==str2); //true
根据上面的结论,我们可以知道,在str1处执行完毕后,会在常量池中创建常量“A”,str2处就可以直接返回已存在的常量“A”。
案例二
//创建:常量池常量“A”
String str1 = "A";
//创建:堆内存对象str2=“A”,常量池中已存在“A”,因此不做任何操作
String str2 = new String("A");
System.out.println(str1==str2); //false
System.out.println(str1==str2.intern()); //true
str1和str2的比较:我们根据日常的工作经验,一眼就可以判断出,他们的==结果是false,但是根据上面的学习和理解,我们能够明白更深层次的原理,就是为什么他们的结果是false ?
分析:str1处会在常量池创建常量“A”,str2在堆中创建对象后,发现常量池中已经存在了常量“A”,所以就不会再做什么操作了(注意要点 3)。
因此,此处的比较可以看作:常量池常量==堆内存对象,由此我们可以得知结果为false。
str1和str2.intern()的比较:因为str2.intern()返回的是常量“A”,所以他们的判断结果是true。
因此,此处的比较可以看作:常量池常量==常量池常量,由此我们可以得知结果为true。
案例三
//创建:堆内存对象str1=“A”,常量池中不存在“A”,因此创建常量池常量“A”
String str1 = new String("A");
//常量池中已存在常量“A”,因此不再做任何操作
str1.intern();
//得到:常量池常量“A”
String str2 = "A";
System.out.println(str1==str2); //false
分析:str1处执行完毕后,会在堆中创建一个对象,然后发现常量池中不存在“A”,所以又在常量池中创建了常量“A”;str1.intern()执行完毕后,其实相当于什么操作都没做,str2的数据来源于常量池的常量。
因此,此处的比较可以看作:堆内存对象==常量池常量,由此我们可以得知结果为false。
案例四
//创建:堆内存(匿名对象)“2”,常量池常量“2” + 创建:堆内存(匿名对象)“2”
//创建:堆内存对象str1=“22”
String str1 = new String("2") + new String("2");
//创建:常量池引用“22”
str1.intern();
//得到:常量池引用“22”
String str2 = "22";
System.out.println(str1 == str2); //true
此处的比较可以看作:堆内存对象==堆内存对象,由此我们可以得知结果为true。
注意:两个new String相加,只会将结果在堆内存中创建对象,不会将结果在常量池中创建
案例五
String str1 = "aaa"; //创建:常量池常量“aaa”
String str2 = "bbb"; //创建:常量池常量“bbb”
String str3 = "aaabbb"; //创建:常量池常量“aaabbb”
String str4 = str1 + str2; //创建:堆内存对象str4=“aaabbb”
String str5 = "aaa" + "bbb"; //得到:常量池常量“aaabbb”
System.out.println(str3 == str4); //false
System.out.println(str3 == str4.intern()); //true
System.out.println(str3 == str5); //true
由此可知:
str3==str4 可看作:常量池常量==堆内存对象 ,所以结果是false
str3==str4.intern() 可看作:常量池常量==常量池常量 ,所以结果是true
str3==str5 可看作:常量池常量==常量池常量 ,所以结果是true
注意:两个字面量字符串相加,只会将相加的结果存入常量池;
注意:两个字符串对象相加,只会将结果在堆内存中创建对象,不会将结果在常量池中创建
案例六
//创建:常量池常量“aabb”
String str1="aa"+"bb";
//创建:堆内存(匿名对象)“a”,常量池常量“a” + 创建:堆内存(匿名对象)“a”
//创建:堆内存对象str2=“aa”
String str2=new String("a")+new String("a");
//创建:常量池引用str2=“aa” 检测到常量池是空,所以将堆中str2的引用放到常量池中
str2.intern();
//创建:堆内存(匿名对象)“aa”,常量池常量“aa”
//创建:堆内存(匿名对象)“bb”,常量池常量“bb”
//创建:堆内存对象str3=“aabb”
String str3=new String("aa")+new String("bb");
//得到:常量池常量“aabb”
str3.intern();
//得到:常量池引用str2=“aa”
String str4="aa";
/**
* 如果在字面量‘aa’+‘bb’时已经加入到常量池中,则下面应该输出false。
* 如果是在常量池中存放的是str2的引用,则下面是true,如果是true,
* 则证明编译器不会将相加字面量加入到常量池中
*/
System.out.println(str2==str4); //true
System.out.println(str1==str3); //false
由此可知:
str2==str4 可看作:堆内存对象==堆内存对象 ,所以结果是true
str1==str3 可看作:常量池常量==堆内存对象 ,所以结果是false
注意:两个字面量字符串相加,只会将相加的结果存入常量池;
注意:两个字符串对象相加,只会将结果在堆内存中创建对象,不会将结果在常量池中创建
案例七
//设置GG和HH常量到常量池,设置HH和GGHH对象到堆
String gghh = "GG"+new String("HH");
System.out.println(gghh==gghh.intern());//true
此处的比较可以看作:堆内存对象==堆内存对象,由此我们可以得知结果为true。
注意:当字面量“GG”和String对象“HH”相加时,字面量“GG”也会在常量池中创建,这和两个字面量相加又是不同的逻辑。
这篇文章就整理到这里了,案例中给出了各种场景,希望能够帮助到跟我有一样困惑的朋友!