Skip to content

01.2 — String 深度解析

定位: Java 中使用频率最高的类 — 不可变性的设计哲学及其在 JVM 中的实现 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 不可变性原理、常量池机制、字符串拼接优化、intern() 行为

一、这是什么?为什么需要它?

是什么

String 是 Java 中表示字符串的类,底层用 final char[] (JDK8) 或 final byte[] (JDK9+) 存储。

java
public final class String
    implements Serializable, Comparable<String>, CharSequence {
    // JDK8: private final char value[];
    // JDK9+: private final byte[] value;
}

为什么需要 String 不可变?

如果 String 是可变的,会出什么问题?

场景 1:并发安全
  String s = "config";
  // 线程 A 读 s,线程 B 同时改 s → 读到一半被修改!

场景 2:常量池复用
  String s1 = "hello";  // 常量池
  String s2 = "hello";  // 复用 s1 的引用
  // 如果 s1 可变,s2 也被改了!

场景 3:HashMap key
  Map<String, String> map = new HashMap<>();
  map.put("key", "value");
  // 如果 String 可变,key 的 hashCode 会变 → map.get("key") 找不到了!

所以不可变性的 4 大动机

动机说明
线程安全不可变对象天然可共享,无需同步
常量池复用相同字面量的引用安全共享
哈希值缓存String 的 hashCode 只计算一次
安全性ClassLoader 路径等关键场景不会被篡改

二、原理拆解

2.1 字符串常量池 (String Pool)

┌─────────────────────────────────────────────────────────────┐
│                  String 常量池机制                             │
│                                                              │
│  String s = "hello";                                         │
│          ┌──────────────────┐                                │
│          │   String Pool    │                                │
│          │  (堆中, JDK7+)    │                                │
│          │                  │                                │
│  s ─────→│ "hello"         │◀──── s2 = "hello" (复用)       │
│          │  "world"         │                                │
│          │  "java"          │                                │
│          └──────────────────┘                                │
│                                                              │
│  String s3 = new String("hello");                            │
│          ┌──────────────┐                                    │
│  s3 ────→│ 堆 (非池)     │                                    │
│          │ "hello"       │  ← 和常量池中的 "hello" 不同对象    │
│          └──────────────┘                                    │
└─────────────────────────────────────────────────────────────┘

创建几个对象?

java
String s = "hello";               // 1个(常量池,无则创建)
String s2 = new String("hello");  // 2个(常量池1个 + 堆1个)
                                  // 如果常量池已有 "hello":1个(堆)

2.2 String 拼接优化

java
// 方式 1:+ 拼接(编译器优化)
String s = "a" + "b" + "c";  // 编译期直接变成 "abc"

// 方式 2:变量拼接
String a = "a";
String b = "b";
String c = a + b;  // 编译器自动用 StringBuilder 优化

// 方式 3:循环拼接
String s = "";
for (int i = 0; i < 1000; i++) {
    s += i;  // ❌ 每次创建 StringBuilder + 新 String
}

循环拼接的字节码分析

s += i 在循环中的真实操作:
① new StringBuilder()
② append(s)
③ append(i)
④ s = toString()  // 新 String 对象

1000 次循环 → 1000 个 StringBuilder + 1000 个 String!
         → 应该手动用 StringBuilder!
java
// ✅ 正确的循环拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
String result = sb.toString();

2.3 intern() 机制

java
String s1 = new String("hello");
String s2 = s1.intern();  // 从常量池获取(或加入)

// intern() 的作用:手动将堆中的 String 放入常量池
// 返回值是常量池中的引用
s1 (堆) ──→ "hello" (堆对象)
s2 ──────→ "hello" (常量池中的)

s1 == s2  → false
s1.equals(s2) → true

2.4 JDK 9 的 byte[] 优化

JDK 8:  char[] value;  → 每个 char 2 字节
JDK 9+: byte[] value;  → 按编码选择

对于大多数字符串(ISO-8859-1/Latin-1),
从 2字节/字符 → 1字节/字符,内存节省约 50%

三、图解全景

┌─────────────────────────────────────────────────────────────┐
│           String、StringBuilder、StringBuffer 对比           │
│                                                              │
│            ┌─────────────────────────────────┐               │
│            │           String                 │               │
│            │  不可变                           │               │
│            │  final char[] (内容不可改)         │               │
│            └─────────────────────────────────┘               │
│                        ↑                                    │
│              (所有字符串操作的基类)                             │
│                        │                                    │
│            ┌───────────┴───────────┐                        │
│            │                       │                        │
│   ┌────────────────┐    ┌──────────────────┐                │
│   │  StringBuilder  │    │  StringBuffer    │                │
│   │  可变           │    │  可变            │                │
│   │  非线程安全     │    │  线程安全(同步)   │                │
│   │  JDK5+ 推荐     │    │  性能差          │                │
│   └────────────────┘    └──────────────────┘                │
│                                                              │
│  选择策略:                                                   │
│  - 不涉及拼接:String                                        │
│  - 单线程频繁拼接:StringBuilder                             │
│  - 多线程共享可变字符串:StringBuffer (罕见)                   │
└─────────────────────────────────────────────────────────────┘

四、实战验证

4.1 常量池引用验证

java
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

System.out.println(s1 == s2);   // true  (同一常量池引用)
System.out.println(s1 == s3);   // false (堆对象 ≠ 常量池)
System.out.println(s1.equals(s3)); // true (内容相等)

// intern
String s4 = s3.intern();
System.out.println(s1 == s4);   // true (intern 返回常量池引用)

4.2 编译期优化验证

java
String a = "hello";
String b = "world";

// 编译期常量折叠
String c = "hello" + "world";   // 编译期优化为 "helloworld"
String d = "helloworld";
System.out.println(c == d);     // true

// 变量拼接(运行时)
String e = a + b;               // StringBuilder 运行时拼接
System.out.println(c == e);     // false
System.out.println(c.equals(e)); // true

4.3 StringBuilder vs 直接拼接性能

java
// 在小规模(<100次)下差异不大
// 在大规模下差异显著

long start = System.nanoTime();
String s = "";
for (int i = 0; i < 10000; i++) {
    s += i;
}
long time1 = System.nanoTime() - start;
// 结果:~500ms 或更差(GC 压力大)

start = System.nanoTime();
StringBuilder sb = new StringBuilder(50000);
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
long time2 = System.nanoTime() - start;
// 结果:~2ms

五、面试视角

追问答案要点
String 不可变到底怎么理解?final char[] 只保证引用不可变,但不保证数组内容不可变。但 String 类不暴露修改方法,且所有方法返回新 String
String s = "a" + "b" + "c" 创建了几个对象?1个。编译期常量折叠为 "abc"
StringBuilder 和 StringBuffer 的区别?前者非线程安全(更快),后者方法加了 synchronized(更慢)
String 的 length() 和 char[] length 有什么关系?length() 返回 value.length;对 Unicode 补充字符 count 不准确——codePointCount() 才准确
JDK 9 String 为什么改为 byte[]?大多数 Latin-1 字符只需 1 字节,char[] 方案浪费 50% 空间

📚 相关链接

  • **数据类型与运算符** — char 类型基础
  • **Object类与通用方法** — equals/hashCode 与 String
  • ← 返回 **语言基础索引**

Knowledge4J — Java 知识库