字符編碼是每一個程序員都會遇到的問題。之前看了很多篇這方面的文章,總感覺講的不是很明白,當時好像看懂了,但過會又講不出個所以然。經過一番思考后,發現字符編碼很容易理解,所以還是要多思考總結,學而不思則罔。那么回到問題上來,什么是字符編碼?
要弄清字符編碼,首先需要知道什么是字符集。從名字來知道字符集就是所有字符構成的集合。例如世界上所有使用的符號構成了一個字符集,這個字符集叫Unicode。你可以在Unicode官方網站上查看所有的字符。值得一提的是emoji表情也被納入了Unicode的范圍。Unicode為所有的字符都分配了一個編號(術語是code points)。這個編號使用"U+<十六進制>"形式表示。你也可以使用和"&#<十進制>;"形式在HTML頁面里插入某個Unicode字符。例如簡體漢字“國”在Unicode的編號是”U+56FD",而繁體漢字“國”在Unicode的編號是"U+570B"。
Java本身就支持Unicode,Java 8支持的Unicode版本為6.2.0。在Java中使用兩個字節來表示一個字符,因此從理論上來說可以覆蓋U+0000到U+FFFF的字符。但是從JDK的Character API來看,好像支持從U+0000到U+10FFFF的字符,有興趣去這里深入了解。Java代碼中可以使用'\u<十六進制>'的形式引入一個Unicode的字符。例如:
char c1 = '\u56FD';
char c2 = '國';
這兩種表示形式是一致的。另外可以使用下面的代碼來檢驗Java是如何來表示一個Unicode字符的:
System.out.println((int)'國'); //輸出為22269
也就是說Java內部其實就是使用Unicode的編號(code points)來表示一個Unicode字符的。
了解了字符集的概念后,那字符編碼是什么?字符編碼是采用一種編碼規則對某個字符集的某個字符的編號(code points)進行編碼。字符編碼一定是關聯著某個字符集的,脫離的字符集來談編碼是沒有意義的。以Unicode字符集為例,有三種對應的字符編碼,分別是UTF-8, UTF-16和UTF-32。平時我們接觸的最多的是UTF-8編碼,UTF-8的編碼規則如下:
UTF-8是一種變長字節編碼方式。對于某一個字符的UTF-8編碼,如果只有一個字節則其最高二進制位為0;
如果是多字節,其第一個字節從最高位開始,連續的二進制位值為1的個數決定了其編碼的位數,其余各字節
均以10開頭。UTF-8最多可用到6個字節。
下圖展示了不同字節下的編碼,x表示字符的內容,即所謂的payload。
1字節 0xxxxxxx
2字節 110xxxxx 10xxxxxx
3字節 1110xxxx 10xxxxxx 10xxxxxx
4字節 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字節 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字節 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
再來回顧下字符編碼的定義:字符編碼是采用一種編碼規則對某個字符集的某個字符的編號(code points)進行編碼。即用編碼規則將字符的編號作為payload進行填充。還是以漢字“國”來舉例:“國”在Unicode的字符編號的十進制數字是22269,轉化為二進制是:1010110 11111101,總共有15比特,因此在UTF-8編碼中需要使用3個字節來表示(1字節只有7比特的payload,2字節有11比特的payload)。我們將“國”按照3字節的編碼要求進行編碼(從低位到高位填充)就得到了“國”的UTF-8編碼(不足為以0補齊即可):11100101 10011011 10111101,其中加粗的就是“國”的編號的二進制表示,未加粗的是UTF-8編碼的標識位。我們用下面的代碼來驗證編碼的正確性:
byte[] bArr = "國".getBytes("utf-8");
for(byte b : bArr){
System.out.print(Integer.toBinaryString(b & 0xFF));
System.out.print(" ");
}
//輸出結果和我們預期的一致,11100101 10011011 10111101
代碼中的(b & 0xFF)是取b的最低字節,因為b在使用Integer.toBinaryString方法時會被強制轉為整型,當b為負數的時候,根據補碼的規則,高位全部會補上1,而我們僅僅關心最低字節,因此做了與操作。
到此為止,相信你應該明白字符編碼是什么東西了吧。