從 String.getBytes 理解 Java 編碼和解碼
背景
週末一直在想 String.getBytes 原理。查閱了一些資料,終於用程式碼驗證了自己的想法。本篇不會涉及太多原始碼相關的知識。
知識點
- Unicode 和 UTF-8 的關係
- 原碼,反碼,補碼
- Java 字串和 Unicode 的關係
- 理解 String.getBytes,解決亂碼問題
- Demo 驗證猜想
用到的工具
原碼,補碼,反碼
因為原碼,補碼,反碼比較簡單,我這裡貼上一個例子進行展示。
圖片內容來源: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 轉換解碼方式獲取字串。