首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 字元編碼深度解析:String.getBytes 與亂碼問題徹底解決

     編輯:Java 綜合教學

Java 字元編碼深度解析:String.getBytes 與亂碼問題徹底解決

字元編碼問題是 Java 開發者必經的坑。本文從 JVM 內部字串儲存機制出發,徹底講透 String.getBytes(charset) 與 new String(bytes, charset) 兩大核心方法原理,並給出 Servlet 參數亂碼、資料庫儲存亂碼等經典場景的標準解決方案。

一、Java 字串內部儲存真相

查看 java.lang.String 原始碼可知:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];  // JDK 8 及之前
    // private final byte[] value; // JDK 9+ Compact Strings
    // private final byte coder;   // JDK 9+ LATIN1/UTF16
}

關鍵結論:

  • JDK 8 及之前:char[] 陣列,每個 char 佔 2 位元組,儲存 UTF-16 代碼單元
  • JDK 9+:byte[] + coder,採用 Compact Strings,單字元語言用 ISO-8859-1 (1 位元組),雙字元語言用 UTF-16 (2 位元組)
  • Java 字串在記憶體中永遠是 Unicode 形式,不存在「GBK 編碼的 String」或「UTF-8 編碼的 String」這種說法

驗證範例:

char c = '你';
System.out.println(Integer.toHexString(c));  // 4f60 (Unicode 碼位)
System.out.println(Integer.valueOf(c));      // 20320 (十進制)
System.out.println(c);                       // 你

二、兩大核心方法原理圖解

1. String.getBytes(Charset charset) - 編碼

功能:將記憶體中的 Unicode 字串,按指定字元集編碼為位元組陣列

過程:Unicode (記憶體) → 編碼器 → byte[] (目標編碼)

String str = "你";
// UTF-8 編碼:3 位元組
byte[] utf8Bytes = str.getBytes("UTF-8");   // [-28, -67, -96] 即 0xE4 0xBD 0xA0
// GBK 編碼:2 位元組
byte[] gbkBytes = str.getBytes("GBK");      // [-76, -88] 即 0xC4 0xE3
// ISO-8859-1 編碼:1 位元組 (無法表示中文,會變成 ? 或亂碼)
byte[] isoBytes = str.getBytes("ISO-8859-1"); // [63] 即 0x3F ('?')

2. new String(byte[] bytes, Charset charset) - 解碼

功能:將位元組陣列按指定字元集解碼為 Unicode 字串

過程:byte[] (來源編碼) → 解碼器 → Unicode (記憶體)

byte[] utf8Bytes = { (byte)0xE4, (byte)0xBD, (byte)0xA0 };
String s1 = new String(utf8Bytes, "UTF-8");   // "你" ✓
String s2 = new String(utf8Bytes, "GBK");     // "涓" ✗ (錯誤解碼)
String s3 = new String(utf8Bytes, "ISO-8859-1"); // "ä½?" ✗

三、經典亂碼場景剖析與解決

場景 1:Servlet 參數亂碼 (GET/POST 請求)

根因:

  1. 瀏覽器以 UTF-8 編碼發送位元組流
  2. Tomcat 預設用 ISO-8859-1 解碼位元組流為 String
  3. 導致中文被錯誤解碼為 ISO-8859-1 字元

標準解法:

// 核心原理:逆向操作還原原始位元組,再正確解碼
String correctStr = new String(wrongStr.getBytes("ISO-8859-1"), "UTF-8");

最佳實踐 (配置而非代碼):

  • Tomcat 8+:在 server.xml <Connector> 加 URIEncoding="UTF-8"
  • Spring Boot:server.tomcat.uri-encoding=UTF-8
  • Filter 方案:CharacterEncodingFilter 設定 forceEncoding=true

場景 2:資料庫讀寫亂碼

檢查鏈路:Java 應用 ↔ JDBC 驅動 ↔ 資料庫連線 ↔ 資料表字元集

// MySQL 連線 URL 關鍵參數
jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull

// 確認資料庫/資料表/欄位字元集
SHOW VARIABLES LIKE 'character_set_%';
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

場景 3:檔案讀寫亂碼

// 讀取:明確指定來源檔案編碼
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream(file), "GBK"))) { ... }

// 寫入:明確指定目標檔案編碼 (推薦 UTF-8)
try (BufferedWriter writer = new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) { ... }

四、編碼轉換核心公式

所有編碼轉換本質都是:String → byte[] → String

// 錯誤認知:直接將 String 從編碼 A 轉編碼 B
// String gbkStr = new String(isoStr.getBytes("GBK")); // 錯誤!

// 正確流程:
// 1. 先用正確編碼還原原始位元組
byte[] originalBytes = wrongString.getBytes("錯誤編碼");
// 2. 再用目標編碼解碼
String correctString = new String(originalBytes, "目標編碼");

五、常用字元集對照表

字元集別名中文支援位元組數主要用途
UTF-8UTF8完美1-4Web 標準、資料庫、檔案、API 傳輸
GBKCP936完美1-2Windows 系統、舊版中文專案
GB2312EUC_CN基本1-2極舊系統、簡體中文子集
ISO-8859-1Latin-1不支援1HTTP 預設、單字節透傳
UTF-16Unicode完美2/4Java 內部、Windows API

六、避坑指南

  1. 永不依賴預設編碼:String.getBytes() 無參版本使用平台預設編碼,跨平台必亂碼
  2. 統一全鏈路 UTF-8:IDE、編譯器、資料庫、伺服器、前端頁面 <meta charset="UTF-8">
  3. 使用 StandardCharsets 常量:str.getBytes(StandardCharsets.UTF_8) 避免拼寫錯誤且編譯期檢查
  4. JDK 9+ Compact Strings:大量 ASCII 字串記憶體佔用減半,升級即享受

核心心法:Java 字串記憶體中只有 Unicode。亂碼本質是「用錯編碼解碼了位元組」。解決之道:找到原始位元組,用正確編碼解碼。

徹底理解編碼原理,不再恐懼亂碼。建議專案建立編碼規範文檔,新成員入職必讀,從源頭杜絕編碼問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved