String 基礎特性
java.lang.String 是 Java 最核心的類之一,特點:
- 不可變:創建後內容永不改變
- final 類:不可繼承
- 字面量池:字符串常量存儲在字符串常量池
- 無需 new 創建:
String s = "abc" 直接創建/復用對象
創建方式與內存差異
| 方式 | 代碼 | 對象數 | 存儲位置 | 備註 |
| 字面量 | String s = "abc" | 0 或 1 | 字符串常量池 | 池中有則復用 |
| new | String s = new String("abc") | 1 或 2 | 堆 + 常量池 | 堆中新建,常量池可能有 "abc" |
| intern() | s.intern() | 0 或 1 | 常量池 | 手動入池,返回池中引用 |
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc").intern();
System.out.println(s1 == s2); // true - 同一池對象
System.out.println(s1 == s3); // false - 堆 vs 池
System.out.println(s1 == s4); // true - intern 返回池對象
System.out.println(s1.equals(s3)); // true - 內容相等
核心方法速查
| 分類 | 方法 | 說明 |
| 長度/訪問 | length() | 字符數 (char 數,非碼點數) |
| charAt(int index) | 單字符訪問 |
| codePointAt(int index) | 碼點訪問 (支持增補字符) |
| 子串/分割 | substring(int begin, int end) | [begin, end) 子串 |
| split(String regex) | 正則分割 |
| split(String regex, int limit) | 限制分割數 |
| subSequence(int start, int end) | CharSequence 接口方法 |
| 查找/匹配 | indexOf(String str) | 首次出現索引 |
| lastIndexOf(String str) | 最後出現索引 |
| contains(CharSequence s) | 是否包含 |
| matches(String regex) | 整體正則匹配 |
| 大小轉換 | toUpperCase() / toLowerCase() | 區域相關,可指定 Locale |
| trim() | 去首尾空白 (≤U+0020) |
| strip() / stripLeading() / stripTrailing() | Unicode 感知去空白 (JDK 11+) |
| 替換/構建 | replace(CharSequence old, new) | 字面量替換 (非正則) |
| replaceAll(String regex, String repl) | 正則替換 |
| format(String fmt, Object... args) | 靜態格式化 |
| 比較/轉換 | equals(Object obj) | 內容相等 (== 比較引用) |
| compareTo(String another) | 字典序比較 (返回 int) |
| toCharArray() / getBytes(charset) | 轉字符/字節數組 |
不可變性深度解析
實現機制
public final class String implements Serializable, Comparable, CharSequence {
private final char[] value; // JDK 8 及之前
// private final byte[] value; // JDK 9+ Compact Strings
private final int hash; // 緩存 hashCode
// ...
}
final char[] value:數組引用不可變,數組內容受保護 (無 setter、無公開訪問)
- 所有「修改」方法返回新 String 對象
不可變性帶來的優勢
- 線程安全:無狀態變更,天然共享
- 常量池復用:相同內容只存一份
- HashCode 緩存:創建時計算一次,Map/Set 鍵高效
- 安全性:網絡/文件路徑、類加載器參數不可被篡改
- 字符串拼接優化:編譯器自動用 StringBuilder
「修改」實際創建新對象
String s = "Hello";
s = s.replace("H", "J"); // 新建 "Jello",s 指向新對象
// 原 "Hello" 若無其他引用,變為垃圾
字符串拼接性能陷阱
循環中拼接:災難性性能
// 反例:O(n²) 創建大量中間對象
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次 new StringBuilder -> append -> toString
}
正確方式:StringBuilder
// 推薦:預估容量,O(n)
StringBuilder sb = new StringBuilder(10000 * 4);
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
編譯器優化:單行拼接自動轉換
String s = "a" + "b" + "c"; // 編譯期常量折疊 -> "abc"
String s2 = a + b + c; // 運行期 -> new StringBuilder().append(a).append(b).append(c).toString()
JDK 9+ Compact Strings 優化
- 內部存儲改為
byte[] + coder (LATIN1/UTF-16)
- 純 ASCII 字符串內存減半
- 大多數西文場景 40-50% 內存節省
- 完全兼容現有 API
字符串常量池與 intern()
// 運行時動態入池
String dynamic = new String("dynamic").intern();
// 現在常量池中有 "dynamic"
// 後續 "dynamic" 字面量或 intern() 返回同一引用
慎用 intern():常量池在永久代/元空間,過度使用導致 OOM。JDK 7+ 池移至堆,風險降低但仍需節制。
常見陷阱
| 陷阱 | 錯誤代碼 | 正確代碼 |
| == 比較內容 | if (s1 == s2) | if (s1.equals(s2)) |
| 循環拼接 | s += x | StringBuilder |
| 忽略編碼 | s.getBytes() | s.getBytes(StandardCharsets.UTF_8) |
| 正則特殊字符 | split(".") | split("\\.") 或 split(Pattern.quote(".")) |
| substring 內存洩漏 (JDK 6) | 大字符串 substring 長期持有 | new String(s.substring(...)) 複製 (JDK 7+ 已修復) |
String vs StringBuilder vs StringBuffer
| 類 | 可變性 | 線程安全 | 性能 | 適用場景 |
| String | 不可變 | 是 | - | 常量、鍵、共享 |
| StringBuilder | 可變 | 否 | 最快 | 單線程構建字符串 |
| StringBuffer | 可變 | 是 (同步) | 較慢 | 多線程構建 (罕用) |
總結
String 作為 Java 基石類,其不可變性設計貫穿線程安全、內存優化、安全性、性能優化等核心優勢。掌握「字面量 vs new」、「== vs equals」、「拼接用 StringBuilder」、「顯式編碼」、「JDK 9+ Compact Strings」等要點,是寫出高質量 Java 代碼的基本功。字符串看似簡單,實則蘊含深厚的系統設計智慧。