字符串常量池
字符串常量池是 JVM 中的一块特殊内存区域,专门用于存储字符串字面量。相同内容的字符串字面量只会存储一份,多个引用共享同一个对象。
存储位置演变
| JDK 版本 | 存储位置 |
|---|---|
| JDK 7 之前 | 方法区(永久代) |
| JDK 7+ | 堆 |
JDK 7 把常量池移入堆,解决了永久代空间不足的问题。
入池规则
| 创建方式 | 是否入池 |
|---|---|
字面量 "hello" | 是 |
| new String("hello") | 否 |
| intern() | 是 |
基本原理
java
public class ConstantPoolDemo {
public static void main(String[] args) {
// 字面量:直接使用常量池
String s1 = "Hello";
String s2 = "Hello";
System.out.println("s1 == s2: " + (s1 == s2)); // true
// new String:不入池,堆中创建新对象
String s3 = new String("Hello");
System.out.println("s1 == s3: " + (s1 == s3)); // false
// intern():手动入池
String s4 = s3.intern();
System.out.println("s1 == s4: " + (s1 == s4)); // true
}
}s1 == s2 为 true,是因为字面量 "Hello" 被放入常量池后,两个变量引用了同一个对象。
s1 == s3 为 false,是因为 new String() 会在堆中单独创建一个对象。
intern() 方法
intern() 方法会将字符串放入常量池,并返回池中的引用。
java
public class InternDemo {
public static void main(String[] args) {
// 编译时常量拼接,结果入池
String s1 = "Hello" + "World";
String s2 = "HelloWorld";
System.out.println("s1 == s2: " + (s1 == s2)); // true
// 变量拼接,运行时计算,不入池
String a = "Hello";
String b = a + "World"; // 实际是 new StringBuilder().append(a).append("World").toString()
String c = "HelloWorld";
System.out.println("b == c: " + (b == c)); // false
// b.intern() 后入池
String d = (a + "World").intern();
System.out.println("d == c: " + (d == c)); // true
}
}编译器会对字面量拼接做优化,直接变成 "HelloWorld",所以 s1 == s2 为 true。但变量拼接是在运行时通过 StringBuilder 完成的,生成的字符串不入池。
内存优化
java
public class MemoryOptimizationDemo {
public static void main(String[] args) {
// 推荐:复用常量池
String s1 = "config";
String s2 = "config";
// 不推荐:创建多个堆对象
String s3 = new String("config");
// 大量相似字符串时 intern() 可节省内存
String[] keys = {"key1", "key2", "key3"};
String[] optimized = new String[keys.length];
for (int i = 0; i < keys.length; i++) {
optimized[i] = keys[i].intern();
}
}
}在大量重复字符串的场景下,合理使用 intern() 可以显著降低内存占用。
