首先去找字符串常量池找,看能不能找到“xyz”字符串的引用,如果字符串常量池中能找不到:
- 创建一个String对象和char数组对象
- 将创建的String对象封装成HashtableEntry,作为StringTable的value进行存储
- 返回创建的String对象
如果字符串常量池中能找到:
- 直接返回找到引用对应的String对象
总结而言就是:
对于String s = new String("xyz");
这种形式创建字符串对象,如果字符串常量池中能找到,创建一个String对象;如果如果字符串常量池中找不到,创建两个String对象。
对于String s = "xyz";
这种形式创建字符串对象,如果字符串常量池中能找到,不会创建String对象;如果如果字符串常量池中找不到,创建一个String对象。
所以,在日常开发中,能用String s = "xyz";
尽量不用String s = new String("xyz");
,因为可以少创建一个对象,节省一部分空间。
需要强调的是,字符串常量池存的不是字符串也不是String对象,而是一个个HashtableEntry,HashtableEntry里面的value指向的才是String对象,为了不让表述变得复杂,我省略了HashtableEntry的存在,但不代表它就不存在。
上文提到的驻留就是新建HashtableEntry指向String对象,并把HashtableEntry存入字符串常量池的过程。
在网上一些文章中,一些作者可能是为了让读者更好的理解,省略了一些这些,一定要注意辨别区分。
达成以上共识之后,我们再回顾一下那个老套的笔试题。
String s1 = new String("xyz");
String s2 = "xyz";
//为什么输出的是false呢?
System.out.println(s1 == s2);
//为什么输出的是true呢?
System.out.println(s1.equals(s2));
有了上面的基础之后,我们画出对应的内存图,s1 == s2为什么是false就一目了然了。
因为equals方法比较的真正的char数据,而s1和s2最终指向的都是同一个char数组对象,所以s1.equals(s2)等于true。
关于他们最终指向的都是同一个char数组对象这一观点,也可以通过反射证明:
我修改了str1指向的String对象的value,str2指向的对象也被影响了。
字符串拼接
现在,我们再来看一下变式题:
String s1 = "aa";
String s2 = "bb";
String str1 = s1 + s2;
String str2 = "aabb";
//为什么输出的是false
System.out.println(str1 == str2);
对于这个题目,我们需要先看一下这段代码的字节码。
字节码指令看不懂没有关系,看我用红色框框起来的部分就行了,可以看到居然出现了StringBuilder。
什么意思呢,就是说String str1 = s1 + s2;
会被编译器会优化成new StringBuilder().append("aa").append("bb").toString();
StringBuilder里面的append方法就是对char数组进行操作,那StringBuilder的toString方法做了什么呢?
从源码中可以看到,StringBuilder里面的toString方法调用的是String类里面的String(char value[], int offset, int count)
构造方法,这个方法做了什么呢?
- 根据参数复制一份char数组对象。复制了一份!
- 创建一个String对象,String对象的value指向复制的char数组对象。
注意,并没有驻留到字符串常量池里面去,这个很关键!!!画一个图理解一下:
也就是说str2指向的String对象并没有驻留到字符串常量池,而str1指向的对象驻留到字符串常量池里面去了,且他们并不是同一个对象。所以str1 == str2还是false
因为复制一份char数组对象,所以如果我们改变其中一个char数组的话,另一个也不会造成影响:
把其中String变成丑比之后,另一个还是帅比,也说明了两个String对象用的不是同一份char数组。
intern方法
上面说到,调用StringBuilder的toString方法创建的String对象是不会驻留到字符串常量池的,那如果我偏要驻留到字符串常量池呢?有没有办法呢?
有的,String类的intern方法就可以帮你完成这个事情。
以这段代码为例:
String s1 = "aa";
String s2 = "bb";
String str = s1 + s2;
str.intern();
在执行str.intern();
之前,内存图是这样的:
在执行str.intern();
之后,内存图是这样的:
intern方法就是创建了一个HashtableEntry对象,并把value指向String对象,然后把HashtableEntry通过hash定位存到对应的字符串成常量池中。当然,前提是字符串常量池中原来没有对应的HashtableEntry。
没了,intern方法,就是这么简单,一句话给你说清楚了。
编译优化
写到这里,好像只有一个坑没有填。就是这个题为什么输出的是true。
final String s3 = "cc";
final String s4 = "dd";
String str3 = s3 + s4;
String str4 = "ccdd";
//为什么输出的是true呢???
System.out.println(str3 == str4);
这道题和上面那道题相比,有点相似,在原来的基础上加了两个final关键字。我们先看一下这段代码的字节码:
又是一段字节码指令,不需要看懂,你点一下#4,居然就可以看到“ccdd”字符串。
原来,用final修饰后,JDK的编译器会识别优化,会把String str3 = s3 + s4;
优化成String str3 = "ccdd"
。
所以原题就相当于:
String str3 = "ccdd";
String str4 = "ccdd";
//为什么输出的是true呢???
System.out.println(str3 == str4);
这样的题目还难吗?是不是那不管str3和str4怎么比,肯定是相等的。
总结
最后
分享一套我整理的面试干货,这份文档结合了我多年的面试官经验,站在面试官的角度来告诉你,面试官提的那些问题他最想听到你给他的回答是什么,分享出来帮助那些对前途感到迷茫的朋友。
面试经验技巧篇
- 经验技巧1 如何巧妙地回答面试官的问题
- 经验技巧2 如何回答技术性的问题
- 经验技巧3 如何回答非技术性问题
- 经验技巧4 如何回答快速估算类问题
- 经验技巧5 如何回答算法设计问题
- 经验技巧6 如何回答系统设计题
- 经验技巧7 如何解决求职中的时间冲突问题
- 经验技巧8 如果面试问题曾经遇见过,是否要告知面试官
- 经验技巧9 在被企业拒绝后是否可以再申请
- 经验技巧10 如何应对自己不会回答的问题
- 经验技巧11 如何应对面试官的“激将法”语言
- 经验技巧12 如何处理与面试官持不同观点这个问题
- 经验技巧13 什么是职场暗语
面试真题篇
- 真题详解1 某知名互联网下载服务提供商软件工程师笔试题
- 真题详解2 某知名社交平台软件工程师笔试题
- 真题详解3 某知名安全软件服务提供商软件工程师笔试题
- 真题详解4 某知名互联网金融企业软件工程师笔试题
- 真题详解5 某知名搜索引擎提供商软件工程师笔试题
- 真题详解6 某初创公司软件工程师笔试题
- 真题详解7 某知名游戏软件开发公司软件工程师笔试题
- 真题详解8 某知名电子商务公司软件工程师笔试题
- 真题详解9 某顶级生活消费类网站软件工程师笔试题
- 真题详解10 某知名门户网站软件工程师笔试题
- 真题详解11 某知名互联网金融企业软件工程师笔试题
- 真题详解12 国内某知名网络设备提供商软件工程师笔试题
- 真题详解13 国内某顶级手机制造商软件工程师笔试题
- 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题
- 真题详解15 某著名社交类上市公司软件工程师笔试题
- 真题详解16 某知名互联网公司软件工程师笔试题
- 真题详解17 某知名网络安全公司校园招聘技术类笔试题
- 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题
资料整理不易,点个关注再走吧
详解17 某知名网络安全公司校园招聘技术类笔试题
- 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题
[外链图片转存中…(img-aW5nBXD4-1631096158110)]
资料整理不易,点个关注再走吧