可以同 == 判断两个string_一文弄懂String常量池,String常见面试题,以及intern()方法...

String做为Java开发中常用的类,弄懂它是非常有必要的,但是往往很多工作了几年从业人员,也并没有特别熟悉过,所以楼主总结一下String的常量池,以及intern()方法等。技术无止境,当然本文也有不足之处,欢迎大家在评论区指正。

前言

本次代码使用 jdk 1.8版本,并且以下代码示例除了第一个写了main()方法,并且所有的示例分别独立运行 ,其余为了简洁做了缺省main()。在创建字符串分析的同时,都默认省略了栈中的句柄指向分析。进入正题之时,先科普几个知识点

String源码里面标注为final修饰的类,是一个不可改变的对象,那平时用到字符串A+字符串B怎么改变了呢,其实这里有涉及到String的常量池,首先常量池存放在方法区。

在jdk1.6时,方法区是存放在永久代(java堆的一部分,例如新生代,老年代)而在jdk1.7以后将字符串常量池移动到了的堆内存中

在jdk1.8时,HotspotVM正式宣告了移除永久代,取而代之的是元数据区,元数据区存放在内存里面(存放一些加载class的信息),但是常量池还是和jdk1.7存放位置一样还是存放在堆中。

先看一波常见面试题:

首先看一道常见的面试题,问输出的是什么?

public 

基本上大家都能知道是false,但是再这么深究一次,问 String s1 = new String("123") 创建了几个对象,String s2 = "123" 创建 了几个对象,那如果题目稍微改变一下成下面这样,那输出的又是什么?

String 

如果对输出结果不是很明白的,本文都会一一解答并且进行拓展。

创建字符串分析:

首先要分析String,一定要知道String几种常见的创建字符串的方式,以及每一种不同的方式常量池和堆分别是什么储存情况。

1.直接写双引号常量来创建

判断这个常量是否存在于常量池,

如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空间对象地址值

如果是常量,则直接返回常量池常量的地址值,

如果不存在,

在常量池中创建该常量,并返回此常量的地址值

String  

2. new String创建字符串

与上面第一种方式相比,第一种方式效率高,下图解决了本文中的最开始出的部分面试题。

首先在堆上创建对象(无论堆上是否存在相同字面量的对象),

然后判断常量池上是否存在字符串的字面量,

如果不存在

在常量池上创建常量(并将常量地址值返回)

如果存在

不做任何操作

String  

3.两个双引号的字符串相加

判断这两个常量、相加后的常量在常量池上是否存在

如果不存在

则在常量池上创建相应的常量(并将常量地址值返回)

如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空间对象地址值,

如果是常量,则直接返回常量池常量的地址值,

String  

如果这里看不懂 intern()方法时,可以快速滑动到文章尾部,先看intern()方法的分析。

4.两个new String()的字符串相加

首先会创建这两个对象(堆中)以及相加后的对象(堆中)

然后判断常量池中是否存在这两个对象的字面量常量

如果存在

不做任何操作

如果不存在

则在常量池上创建对应常量

String 

这个地方比较复杂 ,如果我把String s2 = "123" 代码放在s1.intern()前面先执行,其余代码不变,那么输出结果又为false,这里等会楼主会在分析 intern()方法的时候再重点分析一次。

String 

5.双引号字符串常量与new String字符串相加

首先创建两个对象,一个是new String的对象(堆中),一个是相加后的对象(堆中)

然后判断双引号字符串字面量和new String的字面量在常量池是否存在

如果存在

不做操作

如果不存在

则在常量池上创建对象的常量

String 

6.双引号字符串常量与一个字符串变量相加

首先创建一个对象,是相加后的结果对象(存放堆中,不会找常量池)

然后判断双引号字符串字面量在常量池是否存在

如果存在

不做操作

如果不存在

则在常量池上创建对象的常量

String 

Q: 有人会问为什么两个常量字符串相加得到的对象就会入常量池(参考上面第3点),而加上一个变量就不会???

A: 这是由于Jvm优化机制决定的,Jvm会有编译时的优化,如果是两个常量,Jvm会认定这已经是不可变的,就会直接在编译 时和常量池进行判断比对等,但是如果是加上一个变量,说明最后运行得出的结果是可变的,Jvm无法在编译时就确定执 行之后的结果是多少,所以不会把该结果和常量池比对。

String.intern()方法分析:

在分析intern()方法时候,首先去官网查看api的相关解释

a750fc3f6988e1d4ca96b10def231d58.png

楼主大概翻译一下,意思就是:当调用这个方法时候,如果常量池包含了一个<调用 code equals(Object)>相等的常量,就把该 常量池的对象返回,否则,就把当前对象加入到常量池中并且返回当前对象的引用。楼主用更加白话的方式解释一下:

判断这个常量是否存在于常量池。

如果存在,则直接返回地址值(只不过地址值分为两种情况,1是堆中的引用,2是本身常量池的地址)

如果是引用,返回引用地址指向的堆空间对象地址值

如果是常量,则直接返回常量池常量的地址值,

如果不存在,

将当前对象引用复制到常量池,并且返回的是当前对象的引用(这个和上面最开始的字符串创建分析有点不同)

实战分析问题:

基本上读者看到这里就可以尝试着去回过头文章一些示例代码,看看输出结果,这里分析一下上文存在的一个例子

public 

分析: 1 首先看第一行是两个new String类型的字符串相加(详见上文第4点)可知道,这里创建了堆中有3个对象 一个是1, 一个是23,还有一个是结果 123,由于程序刚启动常量池也没有 1,23 所以会在常量池创建2个对象 (1 , 23)

2 当s1执行intern()方法之后,首先去常量池判断有没有123,此时发现没有,所以会把对象加入到常量池,并且返回 当前对象的引用(堆中的地址)

3 当创建s2时候(详见上文第1点),并且找到常量池中123,并且把常量池的地址值返回给s2

4 由于常量池的地址值就是s1调用intern()方法之后得到的堆中的引用,所以此时s1和s2的地址值一样,输出true。

public 

如果把中间两行换一个位置,那输出就是false了,下面在分析一下不同点,上面分析过的不再赘述。

1.在执行到第二行的时候String s2 = "123"时,发现常量池没有123,所以会先创建一个常量

2.在当s1调用intern()方法时,会发现常量池已经有了123对象,就会直接把123的常量给返回出去,但是由于返回值并没有接 收,所以此时s1还是堆中地址,则输入false;如果代码换成 s1 = s1.intern();那s1就会重新指向常量池了,那输出就为true;

结尾:

由于本文都是在Jdk1.8版本(1.7由于已经把常量池放在堆中了和1.8结果应该一样)执行,如果有读者要探究1.6的相关问题,主要知道jdk1.6在创建String和1.8(1.7)的堆和常量池有一些不同实现,那相关问题就很清楚了,这里楼主没有涉及到1.6的相关问题,以免混淆读者,如果需要探究1.6以及相关问题,欢迎在评论区留言。

楼主很多时候都说了句柄指针的概念相对抽象,如果读者想知道两个对象是否是指向了相同的地址,可用 System.identityHashCode(Object x) 来验证。

————————————————

版权声明:本文为CSDN博主「xzjayx」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/xzjayx/article/details/103020029

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值