文章目录
- 1. 前言
- 2. String的两种创建方式
- 2.1 通过new关键字创建一个字符串对象
- 2.2 采用双引号的方式来创建字符串对象
- 2.3 两种方式的区别
- 3. StringTable的位置
- 4. String的intern()方法
- 5. 判断两个字符串是否相等
- 5.1 equals
- 5.2 ==
1. 前言
String
类是开发中经常使用的一个类。
对String
稍加理解的话,都会听到这样的一个词——字符串常量池(也叫StringTable)
StringTable
是用来存放字符串常量的,当我们使用相同字符串对象的时候,就不需要重新创建字符串对象,而是直接在常量池中获取,这一点和Integer
的缓存有点类似。
2. String的两种创建方式
String
类用得最多的两种创建方式
- 通过new关键字创建一个字符串对象
- 采用双引号的方式来创建字符串对象
2.1 通过new关键字创建一个字符串对象
String s = new String("zhangsan")
通过new关键词创建一个字符串对象,表面上是创建了一个对象,但是实际上创建了两个对象!!
创建的过程如下:
- Java虚拟机会先在字符串常量池中查找有没有
zhangsan
这个字符串对象 - 如果有,就不会在
StringTable
中创建zhangsan
这个字符串对象,并直接在堆中创建zhangsan
这个字符串对象。 - 如果没有,就会在
StringTable
中创建zhangsan
这个字符串对象,并直接在堆中创建zhangsan
这个字符串对象。
到这里可能会感觉到奇怪,为什么要在
StringTable
中创建对象,又在堆中创建对象呢?这是因为字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池。
2.2 采用双引号的方式来创建字符串对象
String s = "zhangsan";
采用双引号的方式来创建字符串对象,创建过程如下:
- 首先,Java虚拟机会先在
StringTable
中查看是否存在zhangsan
这个字符串 - 如果有,直接将
StringTable
中的该字符串的地址返回并赋值 - 如果没,则在字符串常量池中创建该字符串对象,然后将地址返回并赋值
2.3 两种方式的区别
- 对于通过new关键字创建一个字符串对象这种方式来说,不管
StringTable
中是否存在该字符串对象,都需要在堆中创建字符串对象。 - 而对于采用双引号的方式来创建字符串对象这种方式来说,如果
StringTable
中存在该字符串对象,则不需在堆中创建字符串对象
3. StringTable的位置
在Java8之前,StringTable
在永生代中
在Java8之后,移除了永生代,StringTable
被移动到堆中
4. String的intern()方法
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引用是堆中的对象。 - 接着,对s1执行
intern
方法,则会在StringTable
中寻找是否存在zhangsan
这个字符串对象,此时是存在的,于是s2引用是StringTable
中的对象。 - 因此s1和s2的引用地址不一样,最后输出的结果为
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
这个字符串对象。- 使用+号的String字符串拼接,底层其实都是先创建一个
StringBuilder
对象,然后调用append
方法把要+的字符串都append
进去,最后toString
创建一个新的String对象
- 使用+号的String字符串拼接,底层其实都是先创建一个
- 接着,执行s1的
intern
的方法,这时候,会到StringTable
中寻找是否存在zhangsan
这个字符串对象,此时很明显是不存在的,但是堆中存在,因为我这里用的是JDK8,所以字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用(有点懵逼的可以看回上面JDK7前后intern
的区别),也就是说直接保存堆中zhangsan
对象引用。 - 因此s1和s2的引用地址一样,最后输出结果为
true
5. 判断两个字符串是否相等
这是面试的高频考点,判断两个字符串有两种方法
==
操作符用于比较两个对象的地址是否相等。.equals()
方法用于比较两个对象的内容是否相等。
因为equals
是比较内容,所以比较简单,考得最多还是==
5.1 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
类,如果不是就可以直接返回false
- 如果是,则判断两个字符串长度是否相等,如果不相等也就证明两个字符串不相等了
- 如果字符串也相等,那就比较字符串的每个字符是不是相等
5.2 ==
new 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 了