Java中,那些关于String和字符串常量池你需知道的东西(1)

[图片上传失败…(image-7210d9-1611541724305)]

很显然不是JDK的缺陷,JDK虽然确实有设计缺陷,但不至于这么明显,这么愚蠢。

那下面的那个String对象是干什么的呢?

答案是用于驻留到字符串常量池中去的,注意,这里我用了一个 驻留 ,并不是直接把对象放到字符串常量池里面去,有什么区别我们后面再讲。

这里出现了 字符串常量池 的概念,我在 String s = new String(“xyz”)创建了几个实例你真的能答对吗? 中也有过比较详细的介绍,有兴趣的可以去看一下,这里不再重复了。

你只需要知道,字符串常量池在JVM源码中对应的类是StringTable,底层实现是一个Hashtable。

Java中,那些关于String和字符串常量池你需知道的东西

我们以 String s = new String(“xyz”); 为例:

首先去找字符串常量池找,看能不能找到“xyz”字符串对应对象的引用,如果字符串常量池中找不到:

  • 创建一个String对象和char数组对象

  • 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储

  • new String(“xyz”)会在堆区又创建一个String对象,char数组直接指向创建好的char数组对象

如果字符串常量池中能找到:

  • new String(“xyz”)会在堆区创建一个对象,char数组直接指向已经存在的char数组对象

Java中,那些关于String和字符串常量池你需知道的东西

而 String s = “xyz”; 是怎么样的逻辑:

首先去找字符串常量池找,看能不能找到“xyz”字符串的引用,如果字符串常量池中能找不到:

  • 创建一个String对象和char数组对象

  • 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储

  • 返回创建的String对象

如果字符串常量池中能找到:

  • 直接返回找到引用对应的String对象

Java中,那些关于String和字符串常量池你需知道的东西

总结而言就是:

对于 String s = new String(“xyz”); 这种形式创建字符串对象,如果字符串常量池中能找到,创建一个String对象;如果如果字符串常量池中找不到,创建两个String对象。

对于 String s = “xyz”; 这种形式创建字符串对象,如果字符串常量池中能找到,不会创建String对象;如果如果字符串常量池中找不到,创建一个String对象。

image.png

所以,在日常开发中,能用 String s = “xyz”; 尽量不用 String s = new String(“xyz”); ,因为可以少创建一个对象,节省一部分空间。

需要强调的是,字符串常量池存的不是字符串也不是String对象,而是一个个HashtableEntry,HashtableEntry里面的value指向的才是String对象,为了不让表述变得复杂,我省略了HashtableEntry的存在,但不代表它就不存在。

上文提到的驻留就是新建HashtableEntry指向String对象,并把HashtableEntry存入字符串常量池的过程。

在网上一些文章中,一些作者可能是为了让读者更好的理解,省略了一些这些,一定要注意辨别区分。

Java中,那些关于String和字符串常量池你需知道的东西

达成以上共识之后,我们再回顾一下那个老套的笔试题。

String s1 = new String(“xyz”);

String s2 = “xyz”;

//为什么输出的是false呢?

System.out.println(s1 == s2);

//为什么输出的是true呢?

System.out.println(s1.equals(s2));

有了上面的基础之后,我们画出对应的内存图,s1 == s2为什么是false就一目了然了。

Java中,那些关于String和字符串常量池你需知道的东西

因为equals方法比较的真正的char数据,而s1和s2最终指向的都是同一个char数组对象,所以s1.equals(s2)等于true。

关于他们最终指向的都是同一个char数组对象这一观点,也可以通过反射证明:

Java中,那些关于String和字符串常量池你需知道的东西

我修改了str1指向的String对象的value,str2指向的对象也被影响了。

Java中,那些关于String和字符串常量池你需知道的东西

字符串拼接

=====================================================================

现在,我们再来看一下变式题:

String s1 = “aa”;

String s2 = “bb”;

String str1 = s1 + s2;

String str2 = “aabb”;

//为什么输出的是false

System.out.println(str1 == str2);

对于这个题目,我们需要先看一下这段代码的字节码。

image.png

字节码指令看不懂没有关系,看我用红色框框起来的部分就行了,可以看到居然出现了StringBuilder。

什么意思呢,就是说 String str1 = s1 + s2; 会被编译器会优化成 new StringBuilder().append(“aa”).append(“bb”).toString();

StringBuilder里面的append方法就是对char数组进行操作,那StringBuilder的toString方法做了什么呢?

Java中,那些关于String和字符串常量池你需知道的东西

从源码中可以看到,StringBuilder里面的toString方法调用的是String类里面的String(char value[], int offset, int count) 构造方法,这个方法做了什么呢?

  • 根据参数复制一份char数组对象。复制了一份!

  • 创建一个String对象,String对象的value指向复制的char数组对象。

注意,并没有驻留到字符串常量池里面去,这个很关键!!!画一个图理解一下:

Java中,那些关于String和字符串常量池你需知道的东西

也就是说str2指向的String对象并没有驻留到字符串常量池,而str1指向的对象驻留到字符串常量池里面去了,且他们并不是同一个对象。所以str1 == str2还是false

因为复制一份char数组对象,所以如果我们改变其中一个char数组的话,另一个也不会造成影响:

image.png

把其中String变成丑比之后,另一个还是帅比,也说明了两个String对象用的不是同一份char数组。

Java中,那些关于String和字符串常量池你需知道的东西

intern方法

========================================================================

上面说到,调用StringBuilder的toString方法创建的String对象是不会驻留到字符串常量池的,那如果我偏要驻留到字符串常量池呢?有没有办法呢?

有的,String类的intern方法就可以帮你完成这个事情。

以这段代码为例:

String s1 = “aa”;

String s2 = “bb”;

String str = s1 + s2;

str.intern();

在执行 str.intern(); 之前,内存图是这样的:

Java中,那些关于String和字符串常量池你需知道的东西

在执行 str.intern(); 之后,内存图是这样的:

Java中,那些关于String和字符串常量池你需知道的东西

intern方法就是创建了一个HashtableEntry对象,并把value指向String对象,然后把HashtableEntry通过hash定位存到对应的字符串成常量池中。当然,前提是字符串常量池中原来没有对应的HashtableEntry。

没了,intern方法,就是这么简单,一句话给你说清楚了。

关于intern方法,还有一个很有趣的故事,有兴趣的可以去看一下why神的这篇文章 《深入理解Java虚拟机》第2版挖的坑终于在第3版中被R大填平了

编译优化

====================================================================

写到这里,好像只有一个坑没有填。就是这个题为什么输出的是true。

final String s3 = “cc”;

final String s4 = “dd”;

String str3 = s3 + s4;

String str4 = “ccdd”;

//为什么输出的是true呢???

System.out.println(str3 == str4);

这道题和上面那道题相比,有点相似,在原来的基础上加了两个final关键字。我们先看一下这段代码的字节码:

Java中,那些关于String和字符串常量池你需知道的东西

Java中,那些关于String和字符串常量池你需知道的东西

又是一段字节码指令,不需要看懂,你点一下#4,居然就可以看到“ccdd”字符串。

原来,用final修饰后,JDK的编译器会识别优化,会把 String str3 = s3 + s4; 优化成String str3 = “ccdd” 。

image.png

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
存中…(img-IYaopAZP-1715721356863)]

[外链图片转存中…(img-XjbRNYZE-1715721356864)]

[外链图片转存中…(img-e4zPrqB0-1715721356864)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值