字符串常量池是Java运行时环境(JRE)的一部分,它用于存储字符串字面量。字符串字面量是源代码中直接用双引号括起来的字符串,例如"hello"
。在Java中,字符串是不可变的,这意味着一旦创建了一个字符串对象,它的值就不能改变。
当Java编译器遇到字符串字面量时,它会将这些字面量放入字符串常量池中。如果常量池中已经存在相同的字符串字面量,编译器会复用已有的字符串对象,而不是创建一个新的对象。这样做可以节省内存,并提高性能。
一、字符串常量池的工作原理
-
存储位置:
- 在Java 7及之前的版本中,字符串常量池位于方法区的永久代(PermGen)中。
- 从Java 7开始,字符串常量池被移动到堆内存中。但需要注意的是,虽然常量池本身在堆中,但字符串对象的引用仍存储在字符串常量池中。
-
共享性:
- 字符串常量池是全局共享的,即在一个Java虚拟机中,所有类共享同一个字符串常量池。这意味着如果一个字符串对象已经存在于常量池中,它可以在多个类中共享,从而减少内存占用。
-
实现方式:
- 一种是直接使用字符串字面值创建字符串对象并存储在常量池中。
- 另一种是使用String.intern()方法将字符串对象的引用添加到常量池中。如果常量池中已经存在相同的字符串,则直接返回其引用;如果不存在,则将当前字符串的引用添加到常量池中,并返回该引用。
二、字符串常量池的示例
示例1:
String s1 = "red";
String s2 = "red";
System.out.println(s1 == s2); // 输出true
解释:s1和s2都是直接通过字符串字面量创建的,因此它们指向的是字符串常量池中的同一个字符串对象。
示例2:
String s3 = new String("red");
String s4 = new String("red");
System.out.println(s3 == s4); // 输出false
解释:s3和s4都是通过new关键字创建的,它们在堆中各自创建了一个新的字符串对象,因此s3和s4指向的是不同的对象。
示例3:
String s5 = new String("hello");
String s6 = "hello";
String s7 = s5.intern();
System.out.println(s5==s6);//输出false
System.out.println(s6 == s7); // 输出true
解释:s5是通过new关键字创建的,在堆中创建了一个新的字符串对象。由于s6是直接通过字符串字面量创建的,它指向的是字符串常量池中的字符串对象。因此,s5和s6不是同一个对象。注意:intern()方法返回的是该对象的字符串在常量池中的地址,s7的地址:s5对象的字符串在字符串常量池中的地址,因为和s6的字符串是"hello"说明s7和s6的字符串常量池的地址一样,也是同一个对象。
示例4:
String s8 = "1";
String s9 = "1";
String s10 = "1" + "1";
String s11 = s8 + s9;
String s12 = "11";
System.out.println(s10 == s11);//输出false
System.out.println(s10 == s12);//输出true
解释:s10是通过两个“1”拼接而成的,这里s11字符串的连接操作通常是通过StringBuilder
类来实现的,StringBuilder
是一个可变的字符串构建器,它允许高效的字符串连接操作。append
方法会将s8
和s9
的值添加到StringBuilder
对象中,然后toString
方法会将StringBuilder
对象转换为一个不可变的String
对象(该对象是在堆中创建,对象的字符串字面量的引用指向字符串常量池中的“11”的地址)。s10是在字符串字面量创建的,s11是在堆中创建的,所以不是同一个对象。
图解:
示例5:
public class Person {
public String name;
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = new Person();
person2.name="jack";
person1.name="jack";
System.out.println(person1.name== person2.name);//输出true
}
}
结果为true
字符串字面量(例如"jack"
)会被存储在字符串常量池中。当你在代码中创建字符串字面量时,JVM会首先检查字符串常量池中是否已经存在相同的字符串。如果存在,它会返回该字符串的引用;如果不存在,它会创建一个新的字符串并将其添加到字符串常量池中。
person1.name = "jack";
和person2.name = "jack";
这两行代码都使用了相同的字符串字面量"jack"
。由于字符串字面量会被存储在字符串常量池中,所以这两行代码实际上都引用了字符串常量池中的同一个字符串对象,所以返回true。