阿里面试 问我字符串
引言
众所周知在 java 里面除了8
种基本数据类型的话,还有一种特殊的类型 String,这个类型是我们每天搬砖都基本上要使用它。
String 类型可能是 Java 中应用最频繁的引用类型,但它的性能问题却常常被忽略。高效的使用字符串,可以提升系统的整体性能。当然,要做到高效使用字符串,需要深入了解其特性。
String 类
我们可以看下 String 类的源码:
从源码上我们是不是可以发现String
类是被final
关键字所修饰的,String
类的数据是通过char[]
数组来存储的。数组也是被final
修饰的所以String
对象是不可被更改的。接下来我们再看看 String 的一些方法:像 concat、replace、substring 等都是返回了一个新的new String
感兴趣的可以去看看 String 的一些常见方法。当我们执行这些方法之后最原始的字符串是没有改变的,都是返回新的字符串。
输出结果
所以我们只要记住一点:“String
对象一旦被创建就是固定不变的了,
对String
对象的任何改变都不影响到原对象,相关的任何change
操作都会生成新的对象”。
字符串常量池
在JVM
中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为全局字符串常量池(string pool
也有叫做string literal pool
)。
字符串常量池的位置
字符串常量池所在的位置也是跟不同的jdk
版本有关系的。
在
JDK6
及之前字符串常量池存放在方法区, 此时hotspot
虚拟机对方法区的实现为永久代。在
JDK7
字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot
中的永久代。在
JDK8 hotspot
移除了永久代用元空间(Metaspace
)取而代之, 这时候字符串常量池还在堆里只不过把方法区的实现从永久代变成了元空间(Metaspace
) 。
String# intern
String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的 String 对象的引用;
否则,会将此 String 对象包含的字符串添加到常量池中,并且返回此 String 对象的引用。
上述定义出自《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)》我们知道了这个 String::intern()
这个方法的作用下面来看几道并没有什么用的题目看看你是否都能够回答对?
这个代码在JDK6
中输出结果是false
,在jdk7
输出是true
。
为何会因为不同的jdk
版本输出结果不一样,因为不同版本字符串常量池的位置发生了变化。
下面来分析下为何会产生这种差异。
字符串虽然不属于基本数据类型但是它也可以想基本类型一样,直接通过字面量来赋值,同时也是可以通过new
来生成字符串对象。通过字面量赋值的方式和new
的方式 生成字符串还是有区别的。
字面量赋值:通过字面量赋值(使用双引号声明出来的
String
)会先去常量池中查找是否已经有相同的字符串,如果已经存在栈中的引用直接指向该字符串,如果不存在就在常量中生成一个字符串再将栈中的引用指向该字符串。new
的方式创建:而通过new
的方式创建字符串时,就直接在堆中生成一个字符串的对象栈中的引用指向该对象。对于堆中的字符串对象,可以通过intern
() 方法来将字符串添加的常量池中,并返回指向该常量的引用。
jdk6
结果是false
,是因为常量池是在永久代的 Perm 区和 java 堆是两个区域。所以两个区域的对象地址比较是不同的。
JDK7
结果是true
, 这个原因主要是从JDK 7
及以后,HotSpot
将常量池从永久代移到了堆,正因为如此,JDK7
及以后的intern
方法在实现上发生了比较大的改变,JDK7及以
后,intern 方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于如果在常量池找不到对应的字符串则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以为什么返回 true 是因为执行完标号为 1 的时候常量池中没有"java 金融"对象的,接下来标号为 2 的时候 会在常量池生成一个“java 金融”的对象会直接存一个对堆中“*java 金融*”的引用,标号为 3:进行字面量赋值的时候常量池已经存在了所以直接返回该引用。所以都是指向堆中的字符串返回 true。
如果把 3 行代码放到第一行上面结果又不一样了,感兴趣的可以动手试一试并且分析下原因哦。
string 常见性能优化
使用+号拼接字符串
字符串拼接是我们平时在代码中使用最频繁的了。
+号拼接静态字符串
我们可以通过反编译查看下上述代码:
我们可以发现编译器直接帮我们优化了,直接生成了一个字符串“关注公众号:java 金融” 并没有生成中间变量的String
实例。如果我们上述代码稍微变化下
从反编译代码中我们会发现生成了StringBuilder
对象来进行追加。
所以
String
+
拼接变量的时候底层是通过StringBuilder
来实现的,我们循环操作拼接字符串的时候也应当使用StringBuilder
替代+,否则的话每一次循环都会创建
一个StringBuilder
对象。
对于静态字符串的拼接操作,Java 在编译时会进行彻底的优化,会把多个拼接字符串在编译时合成一个单独的长字符串。
常见字符串经典面试题
关于字符串最常见的面试题,面试宝典常见的题目。
String s = new String("xyz") 创建了多少个实例?
一般的回答都会是 2 个,(一个是“xyz”,一个是指向“xyz”的引用对象 s)
答案并没有那么简单哦,可以看看大佬的回答还是非常精彩的。
连接地址https://www.iteye.com/blog/rednaxelafx-774673(文末第一个参考地址)
结束
由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。
https://www.iteye.com/blog/rednaxelafx-774673
https://www.zhihu.com/question/36908414/answer/69724311
https://www.cnblogs.com/paddix/p/5326863.html
https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
https://www.jianshu.com/p/6bee67a7f6ce
https://juejin.im/post/6844903741032759310
评论