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

CoderW喜欢写博客 2021-01-24 14:26:26
java String 字符 字符串 常量


老套的笔试题

在一些老套的笔试题中,会要你判断s1==s2为false还是true,s1.equals(s2)为false还是true。

String s1 = new String("xyz");
String s2 = "xyz";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

对于这种题,你总能很快的给出标准答案:==比较的是对象地址,equals方法比较的是真正的字符数组。所以输出的是false和true。

上面的属于最低阶的题目,没有什么难度。

现在这种老套的题目已经慢慢消失了,取而代之的是有一些变形的新题目:

String s1 = "aa";
String s2 = "bb";
String str1 = s1 + s2;
String str2 = "aabb";
//输出什么呢???
System.out.println(str1 == str2);
final String s3 = "cc";
final String s4 = "dd";
String str3 = s3 + s4;
String str4 = "ccdd";
//又输出什么呢???
System.out.println(str3 == str4);

难度提升了一些,但思考一下也不难得出答案是false和true。

今天的文章就是以这几个题目展开的。

String对象的创建

先简单看一下String类的结构:

image-20210122222326753

可以发现,String里面有一个value属性,是真正存储字符的char数组。

在执行String s = "xyz";的时候,在堆区创建了一个String对象,一个char数组对象。

image-20210123134243855

如何证明创建了一个String对象和一个char数组对象呢?我们可以通过IDEA的Debug功能验证:

15

注意看我截图的位置,在执行完String s = "xyz";之后,再次点击load classes,Diff栏的String和char[]分别加了1,表示在内存中新增了一个char数组对象和一个String对象。

现在,我们再来看String s = new String("xyz");创建了几个对象。

14

从这张Debug动图中,我们可以得出在String s = new String("xyz");之后,创建了两个String对象和一个char数组对象。

又因为String s = new String("xyz");s引用只能指向一个对象,可以画出内存分布图:

image-20210123135550260

从图中可以看到,在堆区,有两个String对象,这两个String对象的value都指向同一个char数组对象。

那么问题来了,下面的那个String对象根本就没被引用,也就是说他没有被用到,那么它到底是干什么的呢?

占了内存空间又不使用,难道这是JDK的设计缺陷?

image-20210123140717179

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

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

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

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

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

image-20210123154722863

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

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

  • 创建一个String对象和char数组对象
  • 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储
  • new String("xyz")会在堆区又创建一个String对象,char数组直接指向创建好的char数组对象

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

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

String s = "xyz";是怎么样的逻辑:

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

  • 创建一个String对象和char数组对象
  • 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储
  • 返回创建的String对象

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

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

总结而言就是:

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

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

image-20210123171825252

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

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

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

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

image-20210123160353095

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

String s1 = new String("xyz");
String s2 = "xyz";
//为什么输出的是false呢?
System.out.println(s1 == s2);
//为什么输出的是true呢?
System.out.println(s1.equals(s2));

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

image-20210123155156910

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

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

image-20210123170204407

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

image-20210123222415172

字符串拼接

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

String s1 = "aa";
String s2 = "bb";
String str1 = s1 + s2;
String str2 = "aabb";
//为什么输出的是false
System.out.println(str1 == str2);

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

image-20210123161329482

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

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

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

image-20210123221900789

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

  • 根据参数复制一份char数组对象。复制了一份!
  • 创建一个String对象,String对象的value指向复制的char数组对象。

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

image-20210123164646528

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

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

image-20210123170038866

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

2abe2cc517a513e4ac5fcec0d3669b22

intern方法

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

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

以这段代码为例:

String s1 = "aa";
String s2 = "bb";
String str = s1 + s2;
str.intern();

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

image-20210123174512400

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

image-20210123174805405

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关键字。我们先看一下这段代码的字节码:

image-20210123171346440
image-20210123171437557

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

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

image-20210123223456766

所以原题就相当于:

String str3 = "ccdd";
String str4 = "ccdd";
//为什么输出的是true呢???
System.out.println(str3 == str4);

这样的题目还难吗?是不是那不管str3和str4怎么比,肯定是相等的。

总结

String对于Java程序员来说就是“最熟悉的陌生人”,你说String简单,它确实简单。你说它难,深究起来确实也有难度,但这些题目,只要你脑海里有一副内存图就会很简单。

面试题也只会越来越难,这个行业看起来也越来越内卷,但只要我学的快,内卷就卷不到我。

好了,今天就写到了,我要去打游戏了。

希望这篇文章,能对你有一点帮助。

写在最后(求关注)

我对每一篇发出去的文章负责,文中涉及知识理论,我都会尽量在官方文档和权威书籍找到并加以验证。但即使这样,我也不能保证文章中每个点都是正确的,如果你发现错误之处,欢迎指出,我会对其修正。

创作不易,为了更好的表达,需要画很多图,这些都是我自己动手用PPT画的,画图也很辛苦的!

image-20210123180803720

所以,不要犹豫了,给点正反馈,答应我,一键三连(关注、点赞、再看)好吗?

我是CoderW,一个程序员。

谢谢你的阅读,我们下期再见!

更多精彩关注微信公众号【CoderW】

版权声明
本文为[CoderW喜欢写博客]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/coderw/p/14320817.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云