12.16 字符編碼與存儲實現:ASCII、GB2312、Unicode、UTF

計算機處理信息分為兩類,一類是數值處理,一類是文本(字符)處理。涉及到文本,就有文本編碼的問題。字符處理包括字符表示、字符存儲、字符顯示(點陣顯示)。字符的表示和存儲就涉及到編碼方案。對於拉丁語系國家來說,使用的字符較少,用8個二進制位的組合即可以表示全部字符了(如英文詞就只是26個字母的組合而已)。對於一些非拉丁語系國家,如中國來說,字符就複雜了。所以字符的編碼有一個逐步發展、完善和統一的過程。最先出現的是ASCII碼,因為計算機最先在美國發展出來,ASCII就是符合美國字符處理需要的字符表示與存儲方案,然後,各國因應自己國家的需要,制定的各自的字符編碼方案(當然會考慮兼容ASCII,如GB2312),最後,當然會走上全球標準化、統一的方向,如Unicode編碼方案及其存儲實現UTF。

1 ASCII

ASCII ( American Standard Code for Information Interchange 美國信息交換標準代碼)是基於拉丁字母的一套字符編碼系統,主要用於顯示現代英語和其他西歐語言,到目前為止共定義了128個字符(2^7),使用8個二進制位,首個二進制位為0。

1.1 控制字符

0~31及127(共33個)是控制字符或通信專用字符(其餘為可顯示字符),如控制符:LF(換行)、CR(回車)、FF(換頁)、DEL(刪除)、BS(退格)、BEL(響鈴)等;通信專用字符:SOH(文頭)、EOT(文尾)、ACK(確認)等;ASCII值為8、9、10 和13 分別轉換為退格、製表、換行和回車字符。它們並沒有特定的圖形顯示,但會依不同的應用程序,而對文本顯示有不同的影響 。

1.2 可顯示字符

32~126(共95個)是字符(32是空格)。

對於數字、字母的編碼如果從二進制去看還是有一定規律的。

1.2.1 十個阿拉伯數字

48~57為0到9十個阿拉伯數字,48的二進制為110000。

1.2.2 大寫英文字母

65~90為26個大寫英文字母,65的二進制為1000001。

97~122號為26個小寫英文字母,97的二進制為1100001。

所以大、小寫字母編碼相差100000(2^5)。

2 GB2312

ASCII對於歐美國家來說,其字符處理完全沒有問題,但對於非拉丁語系國家來說,其字符處理遇到了大問題,對於中文漢字,有七萬多個,常用的就有六千多個。

GB2312-80 是 1980 年制定的中國漢字編碼國家標準。共收錄 7445 個字符,其中漢字 6763 個。GB2312 兼容標準 ASCII碼,採用擴展 ASCII 碼的編碼空間進行編碼,一個漢字佔用兩個字節,每個字節的最高位為 1。具體辦法是:收集了 7445 個字符組成 94*94 的方陣,每一行稱為一個“區”,每一列稱為一個“位”,區號位號的範圍均為 01-94,區號和位號組成的代碼稱為“區位碼”。區位輸入法就是通過輸入區位碼實現漢字輸入的。將區號和位號分別加上 20H,得到的 4 位十六進制整數稱為國標碼,編碼範圍為 0x2121~0x7E7E。為了兼容標準 ASCII 碼,給國標碼的每個字節加 80H,形成的編碼稱為機內碼,簡稱內碼,是漢字在機器中實際的存儲代碼GB2312-80 標準的內碼範圍是 0xA1A1~0xFEFE [7] 。

對於漢字,輸入有輸入碼(如五筆、微軟拼音輸入法),存儲有存儲碼,輸出有輸出碼(如宋體)。

3 Unicode編碼方案

為避免衝突和低效,全球統一一個標準總是一個最佳的選擇,如通信標準、一些零部件的通用標準,字符編譯方案也不例外,Unicode就是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。

UCS-2用兩個字節編碼,UCS-4用4個字節編碼。

UCS-4使用4個字節(從右到左):

第4個字節:組(group),最高位為0,2^7=128個。

第3個字節:平面(plane),2^8=256個。

第2個字節:行 (row),2^8=256個。

第1個字節:碼位(cell),2^8=256個。

如果UCS-4的前兩個字節為全零,那麼將UCS-4的BMP去掉前面的兩個零字節就得到了UCS-2。

Unicode計劃使用了17個平面(第3個字節,0x0-0x10,二進制:0b0-0b10000),一共有17×65536=1114112個碼位(0x0-0x10FFFF)。在Unicode 5.0.0版本中,已定義的碼位只有238605個,分佈在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定義了兩個各佔65534個碼位的專用區(Private Use Area),分別是0xF0000-0xFFFFD和0x100000-0x10FFFD。所謂專用區,就是保留給大家放自定義字符的區域,可以簡寫為PUA。

平面0也有一個專用區:0xE000-0xF8FF,有6400個碼位。平面0的0xD800-0xDFFF,共2048個碼位,是一個被稱作代理區(Surrogate)的特殊區域。代理區的目的用兩個UTF-16字符表示BMP以外的字符。

在Unicode 5.0.0版本中,餘下的99089個已定義碼位(238605-65534*2-6400-2048=99089)分佈在平面0、平面1、平面2和平面14上,它們對應著Unicode定義的99089個字符,其中包括71226個漢字。平面0、平面1、平面2和平面14上分別定義了52080、3419、43253和337個字符。平面2的43253個字符都是漢字。平面0上定義了27973個漢字。

中文範圍 4E00-9FA5:CJK 統一表意符號19968-40869=20901

在Unicode 5.0的99089個字符中,有71226個字符與漢字有關。它們的分佈如下: 如果不算兼容漢字,Unicode目前支持的漢字總數是20924+6582+42711=70217。

字符編碼與存儲實現:ASCII、GB2312、Unicode、UTF

1980年的GB2312一共收錄了7445個字符,包括6763個漢字和682個其它符號。漢字區的內碼範圍高字節從B0-F7,低字節從A1-FE,佔用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。

這6763個漢字在Unicode中不是連續的,分佈在CJK統一漢字字符區(0x4E00-0x9FA5)的20902個漢字中。

1995年的漢字擴展規範GBK1.0收錄了21886個符號,包括21003個漢字和883個其它符號。

這21003漢字包括CJK統一漢字區的20902個漢字。餘下的101個漢字包括:增補漢字和部首80個,包括28個部首和52個漢字。GBK編碼是從FE50-FE7E,FE80-FEA0。

總結一下:GB2312有6763個漢字,GBK有21003個漢字,GB18030-2000有27533個漢字,GB18030-2005有70244個漢字。Unicode 5.0中,如果不算兼容區,目前有70217個漢字。

4 UTF:Unicode編碼的實現方案

我們知道,C++對於整數有不同的類型,如short、int、long,根據不同的長度選擇不同整數類型,以兼顧需要和內存節約的需要。

我們也知道,霍夫曼編碼是一種變長的效率很高的編碼方案,即有編碼的唯一性,又能對使用頻率高的字符使用較短的編碼去實現。

Unicode只是編碼方案,存儲實現可以有不同的存儲方案去實現,可以是定長編碼實現,也可以是變長編碼實現,或者兩者結合。

UTF就是Unicode編碼存儲實現的一個方案,UTF是“Unicode Transformation Format”的縮寫,可以翻譯成Unicode字符集轉換格式,即怎樣將Unicode定義的數字轉換成程序數據。

UTF有UTF-8(變長,1-4個字節)、UTF-16(變長,2個或4個字節)、UTF32(定長,4個字節)。

4.1 UTF-8

UTF-8的編碼方式是變長的,它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度,這樣就解決了存儲浪費的問題,所以UTF-8是Unicode比較好的實現方式。

UTF-8以字節為單位對Unicode進行編碼。從Unicode到UTF-8的編碼方式如下:

Unicode編碼(十六進制) UTF-8 字節流(二進制)

000000-00007F 0xxxxxxx

000080-0007FF 110xxxxx 10xxxxxx

000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx

010000-10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

字符編碼與存儲實現:ASCII、GB2312、Unicode、UTF

UTF-8的特點是對不同範圍的字符使用不同長度的編碼。對於0x00-0x7F之間的字符,UTF-8編碼與ASCII編碼完全相同。UTF-8編碼的最大長度是4個字節。從上表可以看出,4字節模板有21個x,即可以容納21位二進制數字。Unicode的最大碼位0x10FFFF也只有21位(2^20=1114111)。

如“漢”字的Unicode編碼是0x6C49。0x6C49在0x0800-0xFFFF之間,使用3字節模板:1110xxxx 10xxxxxx 10xxxxxx。將0x6C49寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

例如,“漢字”對應的Unicode編碼是0x6c49和0x5b57,在C語言中也可以使用數組來表示:

unsigned char data_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97}; //UTF-8編碼

4.2 UTF-16

我們知道Unicode的範圍為0x0~0x10FFFF。
首先是BMP區間,也就是0x0~0xFFFF這段區間,正好16位就可以表示,也兼容,兩全其美。
那麼超過BMP區間的怎麼辦呢?
也就是0xFFFF~0x10FFFF這段,我們先看這段區間有多少個碼位,0x10FFFF-0xFFFF=0x100000,那麼這個十六進制表示的十進制也就是:1048576個碼位。
我們既然16位存不下,那用32位存咯,因為計算機只能以2的倍數拓展,如果不這麼設計,長短不一,解析起來的效率會非常低。
32位來存這些數字,那麼我們需要怎麼存下呢?簡單的思考過後,大家認為應該分開存儲,也就是將32位分開前16位和後16位,每個16位各存一半。


那麼每一半存的就是1024(由來:√1048576=1024,也就是1024*1024=1048576),1024代表的是2的10次冪,也就是10位二進制數。
這樣就知道了,32位二進制數字中,前後16位中各存10位就夠用了,但是剩餘的6位用來幹什麼呢?
和UTF-8的設計一樣,為了讓識別字符串變得容易,每個字節各用3位來做為識別碼:

110110yyyyyyyyyy 110111xxxxxxxxxx(4個字節)。

54開頭的為32位的前16位。
55開頭的為32位的後16位。
其他開頭的為單16位。
這樣我們就能區分開這三個16位了,在讀取文檔中的任意位置,都能隨意區分出間隔咯。
那麼54開頭的數據區間是多少呢,就是1101 10xx xxxx xxxx,區間就是D800~DBFF。
那麼55開頭的數據區間是多少呢,就是1101 11xx xxxx xxxx,區間就是DC00~DFFF。

為了配合UTF-16,Unicode中也將這兩個區間屏蔽掉,不允許分配任何字符。

如果一個字符的UTF-16編碼的第一個WORD在0xDB80到0xDBFF之間,那麼它的Unicode編碼在什麼範圍內?我們知道第二個WORD的取值範圍是0xDC00-0xDFFF,所以這個字符的UTF-16編碼範圍應該是0xDB80 0xDC00到0xDBFF 0xDFFF。我們將這個範圍寫成二進制:

1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

按照編碼的相反步驟,取出高低WORD的後10位,並拼在一起,得到

1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111

即0xe0000-0xfffff,按照編碼的相反步驟再加上0x10000,得到0xf0000-0x10ffff。這就是UTF-16編碼的第一個WORD在0xdb80到0xdbff之間的Unicode編碼範圍,即平面15和平面16。因為Unicode標準將平面15和平面16都作為專用區,所以0xDB80到0xDBFF之間的保留碼位被稱作高位專用替代。

字符編碼與存儲實現:ASCII、GB2312、Unicode、UTF

4.3 UTF-32

UTF-32 是固定長度的編碼,始終佔用 4 個字節,足以容納所有的 Unicode 字符,所以直接存儲 Unicode 編號即可,不需要任何編碼轉換。浪費了空間,提高了效率。

5 字節序和UTF編碼BOM

字節序有兩種,分別是“大端”(Big Endian, BE)和“小端”(Little Endian, LE)。

Unicode標準建議用BOM(Byte Order Mark)來區分字節序,即在傳輸字節流前,先傳輸被作為BOM的字符。

下表是各種UTF編碼的BOM:

字符編碼與存儲實現:ASCII、GB2312、Unicode、UTF

-End-


分享到:


相關文章: