几张图彻底理解Java字符串常量池、String.intern()(非复制粘贴,准确解释)

引言

实话说,网上95%的文章都是错的,各种复制粘贴,我通过逐一排查以及实验证明,总结了比较准确的字符串在常量池中的存储过程,以及String.intern()方法的分析。全网独一份,最准确没有之一!!


String类常量池存储过程(JDK1.7及以上版本)

首先说明,所有示例图中字符串常量池都画在了堆中,这是因为jkd1.7之后,字符串常量池和静态变量被移动到堆中存放了。

不废话,几幅图直接彻底搞清几种情况:

(1)字面量直接赋值
String s1 = "aaa";
String s2 = "aaa";
String s3 = "bbb";

在这里插入图片描述

解释:栈中存放的是引用,其值为所指向的地址值

(2)new新对象
String s1 = new String("aaa");
String s2 = new String("aaa");
String s3 = new String("bbb");

在这里插入图片描述
注意: jdk1.7及之后虽然把字符串常量池和静态变量移到堆中了,但是!!字符串常量池和new的String对象虽然都在堆中,但并不在同一个地方,也是分开存的!

解释:只要是new,就一定会在堆中创建一个String对象,这是雷打不动的第一条。然后再看常量池中是否已经有该字符串了:

  • 如果没有,就在常量池中创建一个新的String对象,对象里的value属性存具体的字符串本体值(final型char数组)。堆中对象的value属性存指向常量池中对应对象的引用(地址值)
  • 如果有,则直接将堆中对象的value属性置为常量池中对应对象的引用即可

这个逻辑通过String类的构造器源码可验证(网上瞎说的都是没看源码):
在这里插入图片描述
如上,original.value就是将常量池中的char数组地址赋值给堆中String对象里的char数组,数组名赋值属于引用赋值

(3)字符串拼接

这种情况比较特殊,同时也是网上众说纷纭最乱的一个。

String s1 = "aaa";
String s2 = "bbb";
String s3 = "aaabbb";
String s4 = "aaa" + "bbb";
String s5 = s1 + "bbb";
String s6 = "aaa" + s2;
String s7 = s1 + s2;
String s8 = new String("aaa") + new String("bbb");
//常量拼接
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(s3 == s8);	//false

在这里插入图片描述
解释

  • 常量之间的拼接,都不会在堆中创建对象,而直接在字符串常量池中创建对象(如果已经有则直接返回引用);
  • 只要拼接中有任意一个变量(包括new String),就不会在常量池中创建对象,而只在堆中创建,并且其中的value直接存字符串本体值。注意,new String(s1 + “bbb”)这种也算。

String.intern()

Intern()函数作用:一句话,获取字符串常量池中字符串本体值的引用。

  • 如果是字符串常量调用该方法,则直接返回字符串常量池中该常量的引用。(没意义,相当于拷贝了一份)
  • 如果是堆中字符串对象调用该方法,则先判断字符串常量池中是否有该字符串,如果有,直接返回字符串常量池中该常量的引用;如果没有,在常量池中添加一个堆中该字符串对象的引用地址,并返回该引用(即直接返回堆中该字符串对象的地址)。(注意版本区别:JDK1.7之前是在常量池中添加一个副本字符串本体值,然后返回它的引用

一个直观实例:(非常重要!!)

//模拟字符串本体值在堆中而不在常量池中的情况,只能使用拼接来模拟
String s1 = new String("aaa") + new String("bbb");
String s2 = "aaabbb";
System.out.println(s1 == s2);

false

在这里插入图片描述
第一行代码:字符串常量池中创建了2个String对象,value的char数组置为aaa和bbb,堆中也创建了两个String对象,value指向常量池中的对象。最后在堆中再创建了1个拼接后的String对象,并在该对象的value中直接存了aaabbbb本体值。
第二行代码:字符串常量池中创建了1个String对象,value的char数组置为aaabbb,堆中无对象创建

此时s1和s2的值肯定不同,显而易见,一个指向堆中的对象(只要是带变量拼接,都在堆中存字符串本体值),一个指向常量池中的对象。接着我们加上intern操作:

String s1 = new String("aaa") + new String("bbb");
String s3 = s1.intern();
String s2 = "aaabbb";
System.out.println(s3 == s2);
System.out.println(s1 == s2);

true
true

在这里插入图片描述
第一行代码:同上
第二行代码:s1调用intern()函数,首先发现常量池中没有aaabbb这个串,因此在常量池中创建1个新对象,value中的char数组置为堆中本体值的引用地址,之后返回这个地址值给到s3,因此s3也是指向堆中那个对象,所以s1和s3相等。
第三行代码:先判断常量池中有没有aaabbb这个值,发现0x9001这个地址指向的值就是aaabbb,然后直接把这个地址值返回给s2,所以s2也指向了堆中的对象。所以s2和s1相等。

最后说一句,JDK1.7改为常量池中存堆内对象引用的目的:尽量保证字符串的本体值只存一份,无论是在堆里存还是在字符串常量池中存,节省空间消耗。


总结

  1. 字符串本体值只存一份(JDK1.7及以上)
  2. 堆中也可以直接存本体值,仅在带变量拼接的情况下才会出现,其余情况本体值都是在常量池中,单独new的时候会在堆中存一个到常量池的引用
  3. 把图记在脑子里比记文字会牢固的多
  • 21
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值