前言
在我的工作中,常常會遇到形形色色的字符編碼,對于各種編碼技術(shù)本人了解的也不是很多 。
本篇是我了解編碼系列的開篇,主要內(nèi)容講述字符編碼的基本概念,然后介紹一下常見的字符編碼,最后說明一下 Java 中如何編解碼?
什么是字符編碼?
字符編碼也稱字集碼,是把字符集中的字符編碼為指定集合中某一對象(例如:比特模式、自然數(shù)序列、8 位組或者電脈沖),以便文本在計算機中存儲和通過通信網(wǎng)絡(luò)的傳遞 。
編碼及編碼格式
編碼是用預(yù)先規(guī)定的方法將文字、數(shù)字或其它對象編成數(shù)碼,或?qū)⑿畔ⅰ?shù)據(jù)轉(zhuǎn)換成規(guī)定的電脈沖信號 。為保證編碼的正確性,編碼要規(guī)范化、標(biāo)準(zhǔn)化,即需有標(biāo)準(zhǔn)的編碼格式 。常見的編碼格式有 ASCII、ISO-8859-1、GB2312、GBK、GB18030、UTF-8、UTF-16 等 。
常見的字符編碼
ASCII/EASCII
ASCII(American Standard Code for Information Interchange,美國標(biāo)準(zhǔn)信息交換碼)是基于拉丁字母的一套電腦編碼系統(tǒng),主要用于顯示現(xiàn)代英語和其他西歐語言,是現(xiàn)今最通用的單字節(jié)編碼系統(tǒng) 。
EASCII(Extended ASCII,延伸美國標(biāo)準(zhǔn)信息交換碼)是將 ASCII 碼由 7 位擴充為 8 位(增加了 128 個)而成 。EASCII 的內(nèi)碼是由 0 到 255 共有 256 個字符組成 。EASCII 碼比 ASCII 碼擴充出來的符號包括表格符號、計算符號、希臘字母和特殊的拉丁符號 。
ASCII 碼使用指定的 7 位或 8 位二進制數(shù)組合來表示 128 或 256 種可能的字符 。標(biāo)準(zhǔn) ASCII 碼也叫基礎(chǔ) ASCII 碼,使用 7 位二進制數(shù)(剩下的 1 位二進制為 0)來表示所有的大寫和小寫字母,數(shù)字 0 到 9、標(biāo)點符號,以及在美式英語中使用的特殊控制字符 。32~126(共 95 個)是字符(32 是空格),其中 48~57 為 0 到 9 十個阿拉伯?dāng)?shù)字,65~90 為 26 個大寫英文字母,97~122 號為 26 個小寫英文字母,其余為一些標(biāo)點符號、運算符號等 。
ISO-8859-1
【1分鐘帶你認(rèn)識字符編碼 gbk編碼是什么意思】 ISO-8859(拉丁碼表,歐洲碼表)是國際標(biāo)準(zhǔn)化組織(ISO)及國際電工委員會(IEC)聯(lián)合制定的一系列 8 位字符集的標(biāo)準(zhǔn) 。
ISO-8859-1 編碼是單字節(jié)編碼,向下兼容 ASCII,其編碼范圍是 0x00-0xFF,0x00-0x7F 之間完全和 ASCII 一致,0x80-0x9F 之間是控制字符,0xA0-0xFF 之間是文字符號 。
GB2312/GBK/GB18030
GB2312
GB2312《信息交換用漢字編碼字符集》是由中國國家標(biāo)準(zhǔn)總局 1980 年發(fā)布,GB 是 “國標(biāo)” 二字的漢語拼音縮寫,GB2312 編碼適用于漢字處理、漢字通信等系統(tǒng)之間的信息交換,基本集共收入漢字 6763 個(從 B0-F7 是漢字區(qū))和非漢字圖形字符 682 個(其中從 A1-A9 是符號區(qū)) 。整個字符集分成 94 個區(qū)(A1-FE),每區(qū)有 94 個位,總的編碼范圍是 A1-F7 。每個區(qū)位上只有一個字符,因此可用所在的區(qū)和位來對漢字進行編碼,稱為區(qū)位碼 。
GB2312 簡體中文編碼表,GB2312 只是編碼表,在計算機中通常都是用 “EUC-CN” 表示法,即在每個區(qū)位加上 0xA0 來表示 。區(qū)和位分別占用一個字節(jié) 。
舉例來說,“啊”字是 GB2312 之中的第一個漢字,它的區(qū)位碼就是 1601 。字節(jié)編碼,通常采用 EUC 儲存方法,以便兼容于 ASCII 。每個漢字及符號以兩個字節(jié)來表示 。第一個字節(jié)稱為 “高位字節(jié)”,第二個字節(jié)稱為“低位字節(jié)” 。“高位字節(jié)” 使用了 0xA1-0xF7(把 01-87 區(qū)的區(qū)號加上 0xA0),“低位字節(jié)”使用了 0xA1-0xFE(把 01-94 加上 0xA0) 。例如 “啊” 字在大多數(shù)程序中,會以 0xB0A1 儲存(與區(qū)位碼對比:0xB0=0xA0+16,0xA1=0xA0+1) 。

文章插圖
GBK
GBK 全稱《漢字內(nèi)碼擴展規(guī)范》(GBK 即 “國標(biāo)”、“擴展” 漢語拼音的第一個字母) 。GBK 編碼,是在 GB2312-80 標(biāo)準(zhǔn)基礎(chǔ)上的內(nèi)碼擴展規(guī)范,使用了雙字節(jié)編碼方案 。
GBK 亦采用雙字節(jié)表示,總體編碼范圍為 8140-FEFE,首字節(jié)在 81-FE 之間,尾字節(jié)在 40-FE 之間,剔除 xx7F 一條線 。總計 23940 個碼位,共收入 21886 個漢字和圖形符號,其中漢字(包括部首和構(gòu)件)21003 個,圖形符號 883 個 。

文章插圖
GB18030
GB18030 編碼采用單字節(jié)、雙字節(jié)、四字節(jié)分段編碼方案,具體碼位見下文 。GB18030 向下兼容 GBK 和 GB2312 編碼 。
GB18030-2005 收錄了 70244 個漢字

文章插圖
一圖弄懂 ASCII、GB2312、GBK、GB18030 編碼

文章插圖
UTF-8/UTF-16
Unicode
Unicode(統(tǒng)一碼、萬國碼、單一碼),Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語言中的每個字符設(shè)定了統(tǒng)一并且唯一的二進制編碼,以滿足跨語言、跨平臺進行文本轉(zhuǎn)換、處理的要求 。Unicode 通常用兩個字節(jié)表示一個字符,原有的英文編碼從單字節(jié)變成雙字節(jié),只需要把高字節(jié)全部填為 0 就可以 。
Unicode 是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案 。目前的 Unicode 字符分為 17 組編排,0x0000 至 0x10FFFF,每組稱為平面(Plane),而每平面擁有 65536 個碼位,共 1114112 個 。然而目前只用了少數(shù)平面 。UTF-8、UTF-16、UTF-32 都是將數(shù)字轉(zhuǎn)換到程序數(shù)據(jù)的編碼方案 。
最初的 unicode 編碼是固定長度的,16 位,也就是 2 兩個字節(jié)代表一個字符,這樣一共可以表示 65536 個字符(即 0 號平面,基本多文種平面) 。顯然,這樣要表示各種語言中所有的字符是遠(yuǎn)遠(yuǎn)不夠的 。Unicode4.0 規(guī)范考慮到了這種情況,定義了一組附加字符編碼,附加字符編碼采用 2 個 16 位來表示,這樣最多可以定義 1048576 個附加字符,目前 unicode4.0 只定義了 45960 個附加字符 。
Unicode 編碼方案UTF-8
之前提到,Unicode 沒有規(guī)定字符對應(yīng)的二進制碼如何存儲 。以漢字 “漢” 為例,它的 Unicode 碼點是 0x6c49,對應(yīng)的二進制數(shù)是 110110001001001,二進制數(shù)有 15 位,這也就說明了它至少需要 2 個字節(jié)來表示 。可以想象,在 Unicode 字典中往后的字符可能就需要 3 個字節(jié)或者 4 個字節(jié),甚至更多字節(jié)來表示了 。
這就導(dǎo)致了一些問題,計算機怎么知道你這個 2 個字節(jié)表示的是一個字符,而不是分別表示兩個字符呢?這里我們可能會想到,那就取個最大的,假如 Unicode 中最大的字符用 4 字節(jié)就可以表示了,那么我們就將所有的字符都用 4 個字節(jié)來表示,不夠的就往前面補 0 。這樣確實可以解決編碼問題,但是卻造成了空間的極大浪費,如果是一個英文文檔,那文件大小就大出了 3 倍,這顯然是無法接受的 。于是,為了較好的解決 Unicode 的編碼問題,UTF-8 和 UTF-16 兩種當(dāng)前比較流行的編碼方式誕生了 。
UTF-8 是一種針對 Unicode 的可變長度字符編碼,是目前互聯(lián)網(wǎng)上使用最廣泛的一種 Unicode 編碼方式,它的最大特點就是可變長 。它可以使用 1-4 個字節(jié)表示一個字符,根據(jù)字符的不同變換長度 。編碼規(guī)則如下:
- 對于單個字節(jié)的字符,第一位設(shè)為 0,后面的 7 位對應(yīng)這個字符的 Unicode 碼點 。因此,對于英文中的 0-127 號字符,與 ASCII 碼完全相同 。這意味著 ASCII 碼那個年代的文檔用 UTF-8 編碼打開完全沒有問題 。
- 對于需要使用 N 個字節(jié)來表示的字符(N>1),第一個字節(jié)的前 N 位都設(shè)為 1,第 N+1 位設(shè)為 0,剩余的 N-1 個字節(jié)的前兩位都設(shè)位 10,剩下的二進制位則使用這個字符的 Unicode 碼點來填充 。
編碼規(guī)則如下:
Unicode 十六進制碼點范圍
UTF-8 二進制
0000 0000 – 0000 007F
0xxxxxxx
0000 0080 – 0000 07FF
110xxxxx 10xxxxxx
0000 0800 – 0000 FFFF
1110xxxx 10xxxxxx 10xxxxxx
0001 0000 – 0010 FFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
下面以漢字 “漢” 為利,具體說明如何進行 UTF-8 編碼和解碼 。
“漢”的 Unicode 碼點是 0x6c49(110 1100 0100 1001),通過上面的對照表可以發(fā)現(xiàn),0x0000 6c49 位于第三行的范圍,那么得出其格式為 1110xxxx 10xxxxxx 10xxxxxx 。接著,從 “漢” 的二進制數(shù)最后一位開始,從后向前依次填充對應(yīng)格式中的 x,多出的 x 用 0 補上 。這樣,就得到了 “漢” 的 UTF-8 編碼為 11100110 10110001 10001001,轉(zhuǎn)換成十六進制就是 0xE6 0xB7 0x89 。UTF-16
解碼的過程也十分簡單:如果一個字節(jié)的第一位是 0,則說明這個字節(jié)對應(yīng)一個字符;如果一個字節(jié)的第一位 1,那么連續(xù)有多少個 1,就表示該字符占用多少個字節(jié) 。
UTF-16 編碼介于 UTF-32 與 UTF-8 之間,同時結(jié)合了定長和變長兩種編碼方法的特點 。它的編碼規(guī)則很簡單:基本平面的字符占用 2 個字節(jié),輔助平面的字符占用 4 個字節(jié) 。也就是說,UTF-16 的編碼長度要么是 2 個字節(jié)(U+0000 到 U+FFFF),要么是 4 個字節(jié)(U+010000 到 U+10FFFF) 。
那么問題來了,當(dāng)我們遇到兩個字節(jié)時,到底是把這兩個字節(jié)當(dāng)作一個字符還是與后面的兩個字節(jié)一起當(dāng)作一個字符呢?接下來,以漢字 “” 為例,說明 UTF-16 編碼方式是如何工作的 。
這里有一個很巧妙的地方,在基本平面內(nèi),從 U+D800 到 U+DFFF 是一個空段,即這些碼點不對應(yīng)任何字符 。因此,這個空段可以用來映射輔助平面的字符 。
輔助平面的字符位共有 2^20 個,因此表示這些字符至少需要 20 個二進制位 。UTF-16 將這 20 個二進制位分成兩半,前 10 位映射在 U+D800 到 U+DBFF,稱為高位(H),后 10 位映射在 U+DC00 到 U+DFFF,稱為低位(L) 。這意味著,一個輔助平面的字符,被拆成兩個基本平面的字符表示 。
因此,當(dāng)我們遇到兩個字節(jié),發(fā)現(xiàn)它的碼點在 U+D800 到 U+DBFF 之間,就可以斷定,緊跟在后面的兩個字節(jié)的碼點,應(yīng)該在 U+DC00 到 U+DFFF 之間,這四個字節(jié)必須放在一起解讀 。
漢字 “” 的 Unicode 碼點為 0x20BB7,該碼點顯然超出了基本平面的范圍(0x0000-0xFFFF),因此需要使用四個字節(jié)表示 。首先用 0x20BB7-0x10000 計算出超出的部分,然后將其用 20 個二進制位表示(不足前面補 0),結(jié)果為 00010000101110110111 。接著,將前 10 位映射到 U+D800 到 U+DBFF 之間,后 10 位映射到 U+DC00 到 U+DFFF 即可 。U+D800 對應(yīng)的二進制數(shù)為 1101100000000000,直接填充后面的 10 個二進制位即可,得到 1101100001000010,轉(zhuǎn)成 16 進制數(shù)則為 0xD842 。同理可得,低位為 0xDFB7 。因此得出漢字 “” 的 UTF-16 編碼為 0xD8420xDFB7 。Java 中如何編解碼?

文章插圖
下面我們以 “I am 君山” 這個字符串為例介紹 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 編碼格式進行編碼的 。
123456String name = "I am 君山"; byte[] iso8859 = name.getBytes("ISO-8859-1"); byte[] gb2312 = name.getBytes("GB2312"); byte[] gbk = name.getBytes("GBK"); byte[] utf16 = name.getBytes("UTF-16"); byte[] utf8 = name.getBytes("UTF-8"); ISO-8859-1 編碼

文章插圖
ISO-8859-1 是單字節(jié)編碼,中文 “君山” 被轉(zhuǎn)化成值是 3f 的 byte 。3f 也就是 “?” 字符,所以經(jīng)常會出現(xiàn)中文變成 “?” 很可能就是錯誤的使用了 ISO-8859-1 這個編碼導(dǎo)致的 。中文字符經(jīng)過 ISO-8859-1 編碼會丟失信息,通常我們稱之為“黑洞”,它會把不認(rèn)識的字符吸收掉 。
GB2312 編碼

文章插圖
GB2312 字符集有一個 char 到 byte 的碼表,不同的字符編碼就是查這個碼表找到與每個字符的對應(yīng)的字節(jié),然后拼裝成 byte 數(shù)組 。
GBK 編碼
UTF-16 編碼

文章插圖
用 UTF-16 編碼將 char 數(shù)組放大了一倍,單字節(jié)范圍內(nèi)的字符,在高位補 0 變成兩個字節(jié),中文字符也變成兩個字節(jié) 。從 UTF-16 編碼規(guī)則來看,僅僅將字符的高位和地位進行拆分變成兩個字節(jié) 。
UTF-8 編碼

文章插圖
UTF-16 雖然編碼效率很高,但是對單字節(jié)范圍內(nèi)字符也放大了一倍,這無形也浪費了存儲空間,另外 UTF-16 采用順序編碼,不能對單個字符的編碼值進行校驗,如果中間的一個字符碼值損壞,后面的所有碼值都將受影響 。而 UTF-8 這些問題都不存在,UTF-8 對單字節(jié)范圍內(nèi)字符仍然用一個字節(jié)表示,對漢字采用三個字節(jié)表示 。UTF-8 編碼與 GBK 和 GB2312 不同,不用查碼表,所以在編碼效率上 UTF-8 的效率會更好 。
推薦閱讀
- 1分鐘教你amr轉(zhuǎn)換mp3格式 amr文件用什么打開蘋果手機
- 帶你了解此致敬禮含義 書信的此致敬禮是什么意思
- 一分鐘帶你讀懂五線譜 鋼琴符號圖案及解釋對照表
- 教你認(rèn)識譜號和譜表 鋼琴符號圖案及解釋對照表
- 一文帶你了解水凝膜 手機水凝膜的優(yōu)缺點
- 一文帶你正確認(rèn)知寒號鳥 寒號鳥圖片真實圖片
- 1分鐘教你正確的投屏方法 蘋果屏幕鏡像搜索不到電視
- 一文帶你了解丹鳥快遞 丹鳥是什么快遞公司
- 1分鐘記住19個魔方公式秘訣 魔方最后一步公式口訣圖解
- 一文帶你了解步進電機動原理 步進電機驅(qū)動器接線圖
