String类是开发中经常使用的一个类。
对String稍加理解的话,都会听到这样的一个词——字符串常量池(也叫StringTable)
StringTable是用来存放字符串常量的,当我们使用相同字符串对象的时候,就不需要重新创建字符串对象,而是直接在常量池中获取,这一点和Integer的缓存有点类似。
String类用得最多的两种创建方式
String s = new String("zhangsan")
通过new关键词创建一个字符串对象,表面上是创建了一个对象,但是实际上创建了两个对象!!
创建的过程如下:
zhangsan这个字符串对象StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。到这里可能会感觉到奇怪,为什么要在
StringTable中创建对象,又在堆中创建对象呢?这是因为字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池。
String s = "zhangsan";
采用双引号的方式来创建字符串对象,创建过程如下:
StringTable中查看是否存在zhangsan这个字符串StringTable中的该字符串的地址返回并赋值StringTable中是否存在该字符串对象,都需要在堆中创建字符串对象。StringTable中存在该字符串对象,则不需在堆中创建字符串对象在Java8之前,StringTable在永生代中

在Java8之后,移除了永生代,StringTable被移动到堆中

public native String intern();
String的intern方法是一个native方法,它的作用就是如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
intern方法的实现,是JAVA 使用 jni 调用c++实现的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。
**在JDK7之前,**调用intern方法,不管对象在堆中是否已经创建,字符串常量池中仍然会创建一个内容完全相同的新对象。
但是,在JDK7之后,由于字符串常量池放在了堆中,执行 String.intern() 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。
String s1 = new String("zhangsan");
String s2 = s1.intern();
System.out.println(s1 == s2);
上面的代码执行流程如下(假设zhangsan在StringTable中不存在):
StringTable中会创建一个zhangsan字符串,然后堆中也会创建一个zhangsan字符串,s1引用是堆中的对象。intern方法,则会在StringTable中寻找是否存在zhangsan这个字符串对象,此时是存在的,于是s2引用是StringTable中的对象。false
String s1 = new String("zhang") + new String("san");
String s2 = s1.intern();
System.out.println(s1 == s2);
上面的代码的执行流程如下(假设zhang和san在StringTable中不存在)
StringTable中创建两个对象zhang和san,堆中也会创建两个对象zhang和san。还有一个zhangsan的对象(为什么会有这个呢?稍后就会知道了),这时候s1引用的是堆中的zhangsan这个字符串对象。 StringBuilder对象,然后调用append方法把要+的字符串都append进去,最后toString创建一个新的String对象intern的方法,这时候,会到StringTable中寻找是否存在zhangsan这个字符串对象,此时很明显是不存在的,但是堆中存在,因为我这里用的是JDK8,所以字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用(有点懵逼的可以看回上面JDK7前后intern的区别),也就是说直接保存堆中zhangsan对象引用。true
这是面试的高频考点,判断两个字符串有两种方法
==操作符用于比较两个对象的地址是否相等。.equals() 方法用于比较两个对象的内容是否相等。因为equals是比较内容,所以比较简单,考得最多还是==
先看看String的equals源码
public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;
}
String类,如果不是就可以直接返回falsenew String("zhangsan") == "zhangsan"
左侧是在堆中的对象,右侧是在StringTable中的对象,而==比较的是地址,所以这个比较的结果是false
new String("zhangsan") == new String("zhangsan")
左右侧均为new出来的对象,也就是说是两个不同对象,不同对象肯定是不同的内存地址,因此结果是false
"zhangsan" == "zhangsan"
StringTable中不存在StringTable中创建该字符串StringTable中存放了左侧的字符串对象,字符串常量池中只会有一个相同内容的对象,因此为true"zhangsan" == "zhang" + "san"
由于zhang和san都在字符串常量池,所以编译器在遇到+操作符的时候将其自动优化为zhangsan,所以返回 true
new String("zhangsan").intern() == "zhangsan"
new String("zhangsan") 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象intern() 方法的时候发现字符串常量池中已经有了zhangsan这个对象,所以就直接返回字符串常量池中的对象引用了zhangsan比较,会返回 true 了