这是我参与更文挑战的第 9 天,活动详情查看: 更文挑战
StringTable
1.String基本特性
- String是final修饰的,不可被继承。
- String实现了Serializable, Comparable接口,可以序列号和排序。
- String在JDK8以前使用的是char[],在JDK9改用了byte[]。
- String的不可变性:String代表不可变的字符序列。
- 字符串常量池不会存储相同内容的字符串。
- String的StringPool是一个固定大小的Hashtable,JDK7以后默认大小长度是60013,如果存放的String非常多,就会导致Hash冲突,从而导致链表很长,影响调用intern()方法的性能。
2.String的内存分配
String和8种基本类型相同,为了使它们在运行中速度更快,更节省内存,都提供了一种常量池的概念。
常量池就类似一个Java系统级别提供的缓存。8中基本类型的常量池都是系统协调的,String类型的常量池比较特殊,它主要使用的方法有两种。
- 直接使用""定义的字面量会直接存储到常量池。
- 使用String提供的intern()方法。
String内存分配位置的调整过程:
- JDK6及以前,字符串常量池放在永久代
- JDK7字符串常量池放在了堆中
- JDK8的永久代改为元空间,字符串常量池还是放在堆中。
3.String拼接
拼接规则:
- 常量与常量的拼接结果在常量池,原理使编译期优化。
- 常量池不会存在相同的内容
- 只要其中有一个变量,结果就会存储到堆中。原理就是StringBuilder。注意:如果是常量(final)修饰的'变量'拼接,不会使用StringBuilder,依旧会使用编译期优化。
- 如果使用intern()方法,如果常量池有这个字符串那么把这个地址返回,如果没有,就在字符串常量池创建这个字符串。
4.Intern()方法
概述:
如果不使用字面量的方式定义String对象,可以使用String提供的intern方法,Intern()方法会从字符串常量池查询当前字符串是否存在,如果不存在就会放入常量池中。在任意字符串对象上调用String.intern()方法,返回的结果都是指向哪个实例,必须是和常量形式出现的字符串实例完全相同。Intern方法就是确保了字符串对象在内存中只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。
从字节码指令的角度说明new String();到底创建了几个对象?
答:两个对象,其中下标0的地方在堆中创建了一个对象,下标4的时候在运行时常量池创建了一个对象。如果再严谨一点的回答是:先判断常量池是否已经有了当前字符串的对象,如果没有就在堆和常量池各创建一份,如果有就只会在堆中创建。注意:new String("1") + new String("1")这种写法只会在堆空间创建拼接后的字符串对象,不会在常量池创建,因为涉及到字符串拼接JVM会自动创建StringBuilder进行append操作,然后toString()返回,期间并不会生成常量池中的值。但是会在常量池创建单独new String("1") 的字符串"1"。
/**
* 字节码信息
* 0 new #13 <java/lang/String>
* 3 dup
* 4 ldc #2 <abc>
* 6 invokespecial #14 <java/lang/String.<init>>
* 9 astore_1
* 10 return
*/
@Test
public void test4() {
String s1 = new String("abc");
}
String.intern()面试题:下面代码的输出结果
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); //false
String s3 = new String("1") + new String("1"); //注意:这行执行完后 常量池并没有"11"
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); //jdk1.6=false jdk1.7=true
}
答:首先第一个结果不管在哪个版本都是false,因为在new String("1")时,常量池中已经存在了"1"这个对象了,所以调用s.intern()方法不起作用,最后拿着堆空间对象的引用地址和常量中对象的地址比较,结果为·false·。
第二个结果在JDK6和JDK6以后的版本中结果不同,首先new String("1") + new String("1")没有放在常量池,并且JDK6中常量池不在堆空间,拿着new的对象和常量池的对象比较结果为false。JDK7及以后的版本,常量池的位置放在了堆空间,JVM对intern()方法优化,如果堆空间有一个相同的String对象,再次调用intern()方法就会把堆空间已经有的那个对象地址赋给常量池中的对象,所以结果为true。
Intern()方法总结:
- JDK6中,将这个字符串尝试放入常量池中
- 如果池中有这个字符串,那么不会放入,返回这个已有的地址。
- 如果没有,会把该对象复制一份放入池中,并返回地址。
- JDK及以后,将这个字符串尝试放入常量池中
- 如果池中有这个字符串,那么不会放入,返回这个已有的地址。
- 如果没有,会把对象的引用地址复制一份放入池中,并且返回这个引用地址。
- 对于程序中大量的重复字符串时,使用Intern方法可以节省内存空间。存在String的垃圾回收行为。