Java中的String是经常使用的类,但是String中有很多需要学习的东西
1.不可变性
从最开始学习String时,就一直说String的类型是不可变的,但是从来没有深究。
如果看String源码会发现
- String类是final修饰的,使用者并不能通过继承来修改String类中的内容
- String内部维护了一个final修饰的char类型的数组
- char类型的数组虽然是引用类型,但是new String时使用
Arrays.copyOf
来避免使用者通过修改数组内容来修改String - 使用
replace
,substring
是,都是重新new String
2.equals
在Java中使用equals
来比较两个对象是否相等
|
|
Object中的源码中显示如果没有重写equals
,比较的就是两个对象的内存地址是否相等。String重写了equals
方法
从源码中可以发现
- 如果两个String对象地址相同,则肯定相等。
- 两个对象地址不同,如果数组的长度相等,并且数组中每个值都相等,则两个字符串相等。
3.StringBuilder
+
在做变量拼接时其实是用的StringBuilder.append()方法,所以当使用+
和StringBuilder.append()
时,其实效率没有太大的差别,但是如果把+
放在循环中时做字符串循环拼接时,+
的效率就会低很多,如
|
|
因为每次循环都会产生一个StringBuilder对象,通过StringBuilder的append方法完成字符串+
的操作,在循环过程中,
result的长度越来越长,占用的空间越来越大,就比较容易出现OOM。
4.字符串常量池
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 通过字面量声明的字符串直接保存到常量池中,如String s = “test”;
- 当创建字符串时,首先检查字符串常量池中是否存在该字符串
Q1: 下面分别创建了几个对象
|
|
A: s1创建了一个字符串对象;s2可能创建一个对象,也可能创建两个对象,当字符串常量池中不存在”test2”时,在字符串常量池中创建,同时在堆中创建对象,对象指向字符串常量池的”test2”。
可以用String.intern()
方法将字符串保存到常量池中,常量池底层使用StringTable
保存字符串的引用
在使用intern
方法时:
- 如果常量池中已经存在当前字符串,将直接返回当前字符串
- 如果常量池中不存在当前字符串,将该字符串添加到字符串常量池中,然后返回该字符串的引用
|
|
上面代码在JDK1.6和JDK1.7上运行会分别得到不同的结果
JDK1.6下:false, false
JDK1.7下:true, false
是由于JDK1.6中字符串常量池在永久代中,从JDK1.7开始字符串常量池移到了堆中。
JDK1.6中的intern
1.变量a
分配在heap上,a.intern()
指向的是永久代中的引用,和a
指向的不是同一个引用,所以返回false
2.对于b
同理,指向的并不是同一个引用
JDK1.7以后的intern
1.在做intern
操作时,如果StringTable
已经存在相等的字符串,返回StringTable
中的字符串引用,如果不存在,复制字符串的引用到常量池中,然后返回。所以对于变量a
,一开始StringTable
中不存在teststring
的引用,所以JVM会复制变量a
的引用到StringTable
中,所以a.intern()
和a
其实是同一个字符串的引用,返回true
。
2.对于变量b
,一开始就存在java
字符串,b.intern()
返回的是StringTable
中的引用,和b
指向的并不是同一个,所以返回false
。