1. 程式人生 > 程式設計 >從 String.getBytes 理解 Java 編碼和解碼

從 String.getBytes 理解 Java 編碼和解碼

背景

週末一直在想 String.getBytes 原理。查閱了一些資料,終於用程式碼驗證了自己的想法。本篇不會涉及太多原始碼相關的知識。

知識點

  • Unicode 和 UTF-8 的關係
  • 原碼,反碼,補碼
  • Java 字串和 Unicode 的關係
  • 理解 String.getBytes,解決亂碼問題
  • Demo 驗證猜想

用到的工具

進位制之間的相互轉換

Unicode 編碼表

GBK 編碼表

Unicode 官網

原碼,補碼,反碼

因為原碼,補碼,反碼比較簡單,我這裡貼上一個例子進行展示。

圖片內容來源:www.cnblogs.com/yinzhengjie…

Unicode 和 UTF-8 的關係

Uincode 是一個字符集。它規定了我們使用到的字或符號的碼點(code point)。碼點使用 16 進位制儲存。

Uincode 字符集規定 一 的碼點為 4E00。

Uincode 字符集規定 丁 的碼點為 4E01。

計算機呢只能識別二進位制的 0 和 1。而 UTF-8 指的是編碼規則,規定碼點怎麼儲存成二進位制。

還有別的 Unicode 編碼規則,UTF-16 和 UTF-32。

十進位制 Unicode符號範圍(十六進位制) UTF-8編碼方式(二進位制)
0-127 0000 0000-0000 007F 0xxxxxxx
128-2047 0000 0080-0000 07FF 110xxxxx 10xxxxxx
2048-65535 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
65536-1114111 0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

上述表格簡單描述了Unicode 按 UTF-8 編碼的格式。

  • 首先將 16 進位制的碼點,通過進位制轉換 為十進位制
  • 然後使用十進位制的數字查詢上述表格處於哪個範圍中,得出編碼規則。
  • 然後將碼點轉換為 2 進位制,從低位到高位替換 x 即可得到字二進位制的原碼
  • 將二進位制的原碼轉換為補碼儲存。

java 記憶體中的字串採用的是 unicode 編碼,也就是內編碼。我們可以從 unicode 轉變為 GBK 或 UTF-8 等其它規則。

程式碼驗證猜想

以趙為例子講解。

趙的碼點為:8D75

16 進位制的碼點轉換為 10 進位制:36213

36213 處於 2048-65535 ,得出對應的 UTF-8 編碼格式為:1110xxxx 10xxxxxx 10xxxxxx

趙的 16 進位制碼點 8D75 轉換為二進位制 1000

將二進位制填充在 1110xxxx 10xxxxxx 10xxxxxx 中的 x 中,不足的補 0.

11101000 10110101 10110101。

對三個位元組分別求補碼為:

原碼:11101000 10110101 10110101

補碼:10011000 11001011 11001011

補碼對應 java 中的位元組陣列為:{-24,-75,-75}

@Test
public void run454() throws UnsupportedEncodingException {
    String str ="趙";
    final byte[] bytes = str.getBytes("UTF-8");
    StringBuilder stringBuilder =new StringBuilder();

    for (byte aByte : bytes) {
        stringBuilder.append(aByte).append(",");
    }

    System.out.println(stringBuilder.toString());
}
複製程式碼
  • 再加一個例子:

且的碼點:4E14

16 進位制的碼點轉換為 10 進位制:19988

19988 處於 2048-65535 ,得出對應的 UTF-8 編碼格式為:1110xxxx 10xxxxxx 10xxxxxx

16 進位制的碼點轉換成二進位制:100111000010100

原碼:11100100 10111000 10010100

補碼:10011100 11001000 11101100

補碼對應的位元組陣列為:{-28,-72,-108}

@Test
public void run43() throws UnsupportedEncodingException {

    // {-28,-108}
    String str ="且";
    final byte[] bytes = str.getBytes("UTF-8");
    StringBuilder stringBuilder =new StringBuilder();

    for (byte aByte : bytes) {
        stringBuilder.append(aByte).append(",");
    }

    System.out.println(stringBuilder.toString());
}
複製程式碼

GBK 轉碼

趙的 GBK 碼點為:D5D4 十六進位制碼點轉換為二進位制:11010101 11010100 原始碼:11010101 11010100 補碼:10101011 10101100

補碼對應的位元組陣列為:{-43,-44}

@Test
public void run454() throws UnsupportedEncodingException {
    String str ="趙";
    final byte[] bytes = str.getBytes("GBK");
    StringBuilder stringBuilder =new StringBuilder();

    for (byte aByte : bytes) {
        stringBuilder.append(aByte).append(",");
    }
    // -43,-44
    System.out.println(stringBuilder.toString());
}
複製程式碼

JAVA 中亂碼問題

java 字元或字串採用 uincode 作為內編碼。

@Test
public void run44() {
    String str="\u0c2c";
//        బ
    System.out.println(str);
//      ✈
    System.out.println("\u2708");
}
複製程式碼

編碼:字串到位元組。

解碼:位元組到字串。

當我們讀取檔案的時候實際讀取的是位元組。然後根據檔案的編碼格式,將位元組解碼成字串。亂碼問題容易出現的地方就是這裡。

不要妄想將一個亂碼的字串變成一個非亂碼的。這個思路是錯誤的。應該從亂碼之前的位元組著手處理。

@Test
public void run100() throws UnsupportedEncodingException {
    String str = "張";
    final byte[] gbks = str.getBytes("GBK");
    final String s = new String(gbks,"UTF-8");
    System.out.println(s);
}
複製程式碼

上述例子中的 s 已經亂碼了,當你操作這個 s 獲取位元組也是亂碼的。

因此思路是操作 gbks 轉換解碼方式獲取字串。

參考資料

阮一峰字元編碼筆記