在 Java 中,String 類被聲明為 final,其核心字段包括:
private final char value[]:存儲字符數組private final int offset:起始位置偏移量private final int count:字符實際長度private int hash:緩存的哈希值(默認為 0,調用 hashCode() 後計算)這種設計使得 String 實例在創建後不可變,所有修改操作都會返回新對象。
早期 JDK 版本(JDK 7u6 之前)的 substring 採用共享字符數組策略:
public String substring(int beginIndex, int endIndex) {
// 邊界檢查省略...
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
優點:O(1) 時間複雜度,節省內存。缺點:若原字符串極大而子串很小,會導致原數組無法被 GC 回收,造成內存洩漏。JDK 7u6 後改為複製數組方式。
JVM 啟動時通過 Charset.defaultCharset() 確定默認編碼,邏輯如下:
file.encoding(可通過 -Dfile.encoding=utf-8 指定)可通過 System.getProperty("file.encoding") 查看當前值。
String.getBytes(charset) 最终委託給 CharsetEncoder.encode(),核心邏輯在 encodeLoop() 中將字符緩衝區轉換為字節緩衝區。該方法支持流式處理(nio 包特性),雖然單次調用通常不會循環。
以下代碼演示了編碼不一致導致的數據損壞:
String str = "字符串";
// 默認編碼(如GBK)獲取字節
printArray(str.getBytes()); // -41 -42 -73 -5 -76 -82 (6字節)
// 顯式指定UTF-8
printArray(str.getBytes("utf-8")); // -27 -83 -105 -25 -84 -90 -28 -72 -78 (9字節)
// 錯誤示範:GBK字節按UTF-8解碼
System.out.println(new String(str.getBytes(), "utf-8")); // ???
System.out.println(new String(str.getBytes("utf-8"), "gbk")); // 字符??
// 看似恢復實則數據已損壞
System.out.println(new String("瀛楃涓?".getBytes("gbk"), "utf-8")); // 字符??
核心原因:GBK 雙字節編碼漢字,UTF-8 三字節編碼漢字。編解碼編碼不匹配會導致字節序列被錯誤解析,產生替換字符(如 0x3F '?')。雖然表面可通過「雙重轉換」恢復純漢字串,但要求漢字數量為偶數,實際不可靠。
利用 GBK/UTF-8 對 ASCII 字符(字母、數字、%)編碼一致的特性:
// 前端
String encoded = URLEncoder.encode(originalStr, "UTF-8");
// 後端
String decoded = URLDecoder.decode(receivedStr, "UTF-8");
確保傳輸層面字節序列不變,徹底規避編碼不匹配。
在 Web 框架配置中強制指定編碼,例如 WebX:
<webx defaultCharset="UTF-8">...</webx>
同時設置 Tomcat URIEncoding="UTF-8"、數據庫連接字符集、頁面 <meta charset="UTF-8">,實現端到端一致性。