谈谈Java的字符串及其常见问题
一直对Java的字符串概念很模糊,记得很早之前就看到一道经典面试题:“String s = new String(“a”)”产生了几个对象“,看了许多文章都不太明白,并且,由此产生的问题又有”String a = b + c空间上怎么分配“,”String c = “abc”产生了几个对象“等等。这里还是写篇博文将Java字符串有关的知识总结下把🤣
0 Java的字符串类
Java中有三个可以用于表示字符串的类:String
,StringBuilder
,StringBuffer
。
String:字符串常量,底层用了字符数组+final
关键字构成,由于每次改变时,都会生成一个新的String对象,所以是线程安全的。
StringBuilder:继承了AbstractStringBuilder
,可变类,每次修改不会产生新对象,但线程不安全
StringBuffer:同StringBuilder
,但线程安全,支持并发操作
1 String s = new String(“a”)产生了几个对象
这个问题,我更偏向于网上一种答案:产生了一个或两个对象。因为new会在堆中创建一个对象,如果常量池之前没有,则会再创建一个。
2 String::intern
String::intern
是一个本地方法,作用是
- 如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用
- 否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用
由此产生的一段代码
1 |
|
这段代码在JDK6
中输出为false
,在jdk7
输出为true
这是因为在JDK6
时,常量池是放在方法区永久代中的,而new
生成的对象引用在堆中,返回的地址当然不一样
而在JDK7
时,将常量池移到了堆中,使用intern
方法会先查询常量池中是否存在,如果存在,则放会常量池引用,但不存在,不会将字符串拷贝到常量池,而是在常量池中生成一个对字符串的引用。
所以在示例中,执行第1
行时常量池是没有hello world
的,但执行了intern
之后就会将其引用放到常量池中,然后执行第3
行返回的就是该引用。结果就是true
了。但如果将2
和3
对换位置就不一样了。
3 拼接的String是否相等
考虑以下代码
1 |
|
答案是两个都是true
。“a”
,”b”
,”c”
三个本来就是字符串常量,进行+符号拼接之后变成了“abc”
,“abc”
本身就是字符串常量(Java中有常量优化机制),所以常量池立马会创建一个“abc”
的字符串常量对象,在进行st2=”abc”
,这个时候,常量池存在“abc”
,所以不再创建。所以,不管比较内存地址还是比较字符串序列,都相等。
4 String类拼接数据是否相等
考虑以下代码
1 |
|
答案是false
,true
。第二个为true
很好理解,但第一个为什么为false
呢?
因为在Java任何数据和字符串进行(+)运算的原理是StringBuilder
或者StringBuffer
类和里面的append
方法实现拼接,然后调用toString()
把拼接的对象转换成字符串对象,最后把得到字符串对象的地址赋值给变量。
大致流程为:
- 常量池创建
“ab”
对象,并赋值给st1
,所以st1
指向了“ab”
- 常量池创建
“abc”
对象,并赋值给st2
,所以st2
指向了“abc”
- 由于这里走的(+)的拼接方法,所以第三步是使用
StringBuffer
类的append
方法,得到了“abc”
,这个时候内存0x0011表示的是一个StringBuffer
对象,注意不是String
对象。 - 调用了
Object
的toString
方法把StringBuffer
对象装换成了String
对象。 - 把
String
对象(0x0022)赋值给st3
总结
不得不说,Java的八股知识点是真的多呀,一个字符串这么多规矩,不过解决了这些问题感觉也对Java的字符串认识更加深刻了。😁