JDK1.8+的String(字符串)常量池理解

        在工作中使用String的地方很多,但是有时候会产生一点疑问,那就是项目中用到了这么多String,会不会对内存造成一定的压力,会不会降低程序的性能,字符串的值都存放到哪里去了?所以就深入了解了一下。

        然而网上搜索到的内容很多很全面,但这意味着很杂,难以把握,不利于理解和吸收,基于以上原因,我查阅了众多技术博客,总结了一下基于JDK1.8.0_211的测试结果和理论。

        先来理解一下:【堆、栈、字符串常量池】 ,这里只聊涉及到字符串常量池的地方。

        JDK1.8之前的不管,主要是JDK1.8+之后的版本,因为这也是我目前在用的版本。本文的所有结论,方法作用,jvm的内存,都是指的JDK1.8+的版本。

在Java的JVM的内存中分为3个区:堆、栈和方法区。

栈区
栈中分配的是基本数据类型和自定义对象的引用。

堆区
堆中分配的是对象,也就是new出来的东西, 被所有线程共享。其中包含了本篇文章的重点字符串常量池

方法区(此篇文章不涉及方法区)
又叫静态区,存放的是类信息和static变量、常量,被所有线程共享。

f80f1244a5f7414491720a8e304821f0.jpeg

结论

 一、只在常量池创建常量

String str = "A";

我们平时在方法中,会声明一个String类型的变量,比如上面这种。

那么,str对象就会被存放到中,字面量 “A” 就会被创建到中的字符串常量池中。

feb0f9c8bf0a437e9f4c9943ca695a7c.jpeg

二、在堆上创建对象,在常量池创建常量

String str = new String("A");

这种方式,会在中创建一个对象str,也会在常量池中创建一个常量"A"。

b3fa7dfbd4b54857ad590a06ed0c0bf8.jpeg

三、只在堆上创建对象

String str = new String("A") + new String("B");

这种方式,会在中创建三个对象:两个匿名对象,分别代表“A”和“B”;一个str对象,代表“AB”。

也会在常量池中创建两个常量,“A”和“B”。但是不会创建常量“AB”。(图片有所简化)

640214928bc74e2589c6e49192fd42aa.png

四、在堆上创建对象,在常量池上创建引用

String str = new String("A") + new String("B");
str.intern();

可以发现,这种方式,是在第三点的基础上,加了一行代码。

这行代码的作用是:如果常量池里是空的(不存在常量“AB”),就在常量池里面创建一个引用(指向中的对象str);非空,不做任何操作。返回值是常量池里的内容。

6bba7853772d4c8fb2e013014ab98297.jpeg

注意要点:

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”也会在常量池中创建,这和两个字面量相加又是不同的逻辑。

这篇文章就整理到这里了,案例中给出了各种场景,希望能够帮助到跟我有一样困惑的朋友!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值