String、StringBuffer、StringBuilder相关性质和面试题
String基本性质
- 可以字面量赋值,也可以通过new造对象赋值;
- String声明为
final
类型,不可继承,且具有不可变性; - String底层用
final
修饰的value[]
数组存储,在jdk8.0之前用char[]数组,jdk9.0后用byte[]
数组存储;(why?因为使用发现大部分String类型能够被一个字符存储下来,虽然汉字不行,但是大部分还是英文类型居多,这样用byte数组存储能明显节省空间); - String实现了Serializable接口和Comparable接口;
String的不可变性和StringTable
String s1 = "abc";
String s2 = "abc";
System.out.println(s1==s2);//true
- 上述字符串的创建是以字面量的形式赋值的,而字面量在类的加载后是存储在字符串常量池
StringTable
中的。
在jdk6.0时,字符串常量池是放在方法区中(此时方法区称为永久代,占用虚拟机内存);jdk7.0时,字符串常量池被转移到堆中;jdk8.0后,字符串常量池仍然被保留在堆中(而永久代的概念被元空间取代,占用本地内存)。
问:为什么字符床常量池的位置需要从方法去到堆中区?
答: 因为如果StringTable放在方法区中,只有在full GC的时候才会回收空间,回收的效率并不高。实际中字符串会被大量的创建,由于回收效率低,更加容易导致方法区内存不足,而放在堆中,可以利用minor/major/full gc回收,回收效率更高。
StringTable
的一个特点是不能存放两个相同内容的字符串的,且不可更改(只能重新创建)。在jdk7.0之前,StringTable
底层是固定大小的Hashtable
,长度为1009,所以不存在相同的两个字符串。jdk7.0之后,长度不固定,默认设置为60013。当字符串常量池中存储的字符串过多时,会导致哈希冲突严重,导致链表的长度变长,从而导致在调用String.intern()时降低性能。
结合上面两点可以得出,变量s1和s2指向的是字符串常量池中的同一个“abc”
,因此判断结果为true
。
String s1 = "abc";
String s2 = "abc";
s1 = "hello";
//s1 += "def";拼接/replace函数同样是需要在StringTable重新创建
System.out.println(s1 == s2);//false
System.out.println(s1);//hello
System.out.println(s2);//abc
如果给s1重新赋值“hello”
,会在字符串常量池新建一个“hello”
,然后s1的指针指向“hello”
,所以判断结果为false
。
- +=拼接/replace()函数同样是需要在StringTable重新创建;
问:String对象的创建通过字面量和new的方式两者有什么不同?
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2);