目錄算法
一、字符和編碼編程
二、ISO/IEC 10646和Unicode網絡
三、UTF-16編碼
四、UTF-8spa
五、UTF-32debug
六、UCS-2和UCS-4設計
附錄1:16進制數與二進制數、10進制數的換算調試
附錄2:查看字符編碼的方法code
漢字、英文字母、數字符號、標點符號等文字符號統稱爲字符。計算機本質上只能處理數值,每一個字符在計算機中只能用一個整數來表示,這種整數稱爲字符編碼。字符編碼固然必須統一制定,但歷史上各類文字的字符編碼都是由使用該種文字的國家或地區自行制定,例如美國爲英文字符制定的編碼ASCII,中國大陸爲漢字制定的編碼GB2312和GBK,港澳臺地區使用的繁體漢字編碼Big5等等。這些編碼使得文字能夠在計算機中處理和存儲,而且在使用一樣編碼的地區能夠互相交流計算機文字文檔。可是使用這種編碼的文字一旦在不一樣的地區之間互相交流,就會出現問題。由於編碼是各自獨立編制的,編碼空間不免出現重疊,即,一樣的一些編碼在不一樣的編碼標準中將表示不一樣的字符。內存
以漢字爲例。1981年發佈的漢字編碼標準GB2312只收錄簡體漢字,共有7445個字符,全體字符劃分爲94個區,每一個區94個字符,每一個字符由其所在的區號和區內位號惟一肯定。區號加位號能夠做爲一種編碼,這種編碼稱爲區位碼。例如,漢字「碼」位於34區的第75位,其區位碼爲3475。區位碼不能直接在機器中使用,由於它的編碼空間與ASCII重疊,若是文字中既有漢字又有英文,就會產生矛盾。例如,「碼」的區位碼在機器中將是兩個字節,第一個字節是34,第二個字節是75。然而在ASCII中,編碼34表示字符「」」,75表示字符「K」,因而,34和75兩個數字到底是表明漢字「碼」,仍是表明兩個ASCII字符「」」和「K」?爲了解決這個問題,GB2312的字符在機器中其實是採用機內碼來表示。機內碼和區位碼有簡單的對應關係:將區位碼的區號和位號分別存放在兩個字節中,而後每一個字節都加上0xA0(16進制數)便可。機內碼使得漢字編碼的空間爲0xA1A1到0xF7FE,每一個漢字的編碼爲兩個字節,每一個字節的編碼在0xA1到0xFE之間,而ASCII的編碼是一個字符一個字節,每一個字節的編碼在0x00到0x7F之間,這樣就使得英文和漢字的編碼空間再也不重疊。
釋疑:本章中出現的數字,凡是用「0x」開頭的即爲16進制數,沒有的則爲10進制數。不熟悉16進制數的讀者可參閱本章的附錄。
GB2312收集的漢字太少,而且只有簡體字。1995年電子科技部質量司和國家技術監督局標準化司頒佈了一個指導性規範GBK,其中包含了GB2312的所有字符,而且這些字符的編碼就是原來的機內碼,同時還包括了Big5中的所有字符以及日文、韓文中使用的漢字。Microsoft的Windows中文簡體版從Windows 95開始全面支持GBK,這使得GBK成爲事實上的工業標準。GBK的編碼空間從0x8140到0xFEFE,每一個漢字的編碼仍爲兩字節,其中第一個字節8位二進制數的最高位始終是1,這使得GBK的編碼空間能夠避開ASCII的編碼空間。機器只需從文檔的第一個字節開始判別,若是這個字節的最高位是0,那麼這是一個ASCII字符,若是最高位是1,那麼連同其後的一個字節表示一個GBK字符。
可是GBK的編碼與Big5的編碼不一樣,而且Big5的編碼空間是0xA140到0xFEFE,二者的編碼空間是重疊的,這使得一樣一個編碼,在GBK中表示一個字符,而在Big5中表示的倒是另一個字符。
解決這種問題的最好辦法是統一編碼,無論簡體字仍是繁體字,還有日文、韓文中使用的漢字,所有統一編碼,使得每一個編碼對應的漢字是惟一的,而且與世界上全部其它語言的字符編碼不重疊。
ISO/IEC 10646和Unicode都是全球字符統一編碼標準,ISO/IEC 10646是國際標準化組織頒佈的標準,Unicode是一個稱爲Unicode協會的工業組織發佈的標準。這兩套標準並非徹底相同的,可是在字符編碼方面,二者是相同的。ISO/IEC 10646和Unicode都會不斷更新並推出新版本,但這兩個標準將在字符編碼方面保持一致,而且版本更新也保持同步。例如,Unicode 3.0與ISO/IEC 10646-1第二版同步,Unicode 4.0與ISO/IEC 10646第三版同步。
ISO/IEC 10646和Unicode收集了世界上全部書寫語言所使用的字符,包括當前正在使用的和古代的,將全部這些字符統一編碼。其編碼方法是首先將全部的字符排列在若干個平面上,每一個平面劃分爲256行,每行256列,從而每一個平面存放64K(1K=1024)個字符。每一個字符所在的平面號、行號和列號稱爲碼位(code point),碼位可做爲字符的編碼。例如,「碼」字在編號爲0的平面上,位於第120行第1列,其平面號、行號和列號分別用十六進制數表示就是0x00、0x78和0x01,所以其碼位爲0x007801。每一個平面的行號和列號都從0開始數起,所以行號和列號的範圍都是0x00到0xFF,平面內字符的行號加列號編碼範圍從0x0000到0xFFFF。因而平面0的字符碼位是0x000000~0x00FFFF,平面1的字符碼位是0x010000~0x01FFFF,平面2的字符碼位是0x020000~0x02FFFF,餘類推。
平面0是最重要的平面,稱爲BMP(Basic Multilingual Plane),其中基本上包括了當今世界上各類語言使用的字符,固然也包含中、日、韓三種文字中使用的全部漢字。
BMP中的第0行的字符及其碼位編碼徹底與Latin 8859-1相同,特別是,其中碼位編碼0x0000到0x007F及其對應的字符徹底與ASCII中的字符及其編碼相同。
Unicode中進行編碼的漢字不只有中國大陸、港澳臺地區使用的漢字,也有日文、韓文中使用的漢字,這三個民族使用的漢字儘管有些字的字形相同,但其含義卻千差萬別。爲此,對這些漢字按必定的規則進行甄別和統一,被承認爲同一個字的字符給予惟一的一個碼位,不承認的字,即便字形相同,也做爲不一樣的字符給予不一樣的碼位。這樣聚集起來的漢字字符稱爲中日韓統一漢字(CJK Unified Ideographs)。在BMP中,CJK統一漢字的碼位範圍以下:
0x3000~0x303F:CJK標點符號,例如【、】、《、》等。
0x3190~0x319F:CJK筆劃。
0x3200~0x32FF:用圓圈圈起來的一些字符和數字,例如㈠、㈥、㊣等。
0x3300~0x33FF:CJK兼容漢字。這是一些表示日期、時間和度量衡的符號,做爲單個漢字,例如㎎、㎏、㏎等。
0x4E00~0x9FFF:CJK統一漢字,涵蓋了GB23十二、GB8565和《現代漢語通用字表》的所有漢字,Big5和臺灣電報碼的所有漢字,還有日本JIS、韓國KSC中的所有漢字。
平面0中的碼位0xD800~0xDFFF沒有對應任何字符,這個區段稱爲替換區(surrogate),留給UTF-16編碼使用。
雖然ISO/IEC 10464和Unicode對全球字符統一地安排了碼位,可是若是直接將碼位做爲編碼,則在機器中使用仍存在一些問題。理論上ISO/IEC 10646預留了64K個平面,這樣平面號的編碼範圍將爲0x0000~0xFFFF,加上平面內的行號和列號,字符碼位的範圍將是0x00000000~0xFFFFFFFF,也就是說,必須用4個字節來存放一個字符的碼位。Unicode僅使用編號0x00到0x10的17個平面,其碼位範圍爲0x000000~0x10FFFF,也須要用3個字節來存放。可是咱們實際使用的字符絕大多數都在BMP中,只是偶爾使用其它平面的字符,所以能夠設計一種兩字節的編碼,使得BMP中的字符編碼就是它們在BMP中的碼位,而其它平面的字符用一種代替的方法來表示,這樣的編碼就是UTF-16。
RFC2781詳細描述了UTF-16的編碼算法,這個算法並不複雜:
設當前要編碼的字符爲ch,其Unicode碼位爲U,,
1) 若是U<0x10000,即ch是BMP中的字符,則U自己就是ch的UTF-16編碼。換句話說,BMP中的字符,其UTF-16編碼就是碼位。
2) 不然,令。因爲,因此必有,於是U2必定能夠用20位二進制數來表示。設U2對應的二進制數爲yyyyyyyyyyxxxxxxxxxx。
3) 構造兩個16位整數W1和W2:W1=110110yyyyyyyyyy,W2=110111xxxxxxxxxx,這兩個整數就是字符ch的UTF-16編碼,總共4個字節:
110110yy yyyyyyyy 110111xx xxxxxxxx
注意對於BMP以外的字符,對應的W1和W2分別知足和,都落在平面0的替換區內,不會與其它BMP字符的碼位重疊。
將UTF-16編碼轉換爲Unicode碼位的逆算法也是簡單的:
設當前要轉換的16位UTF-16編碼是W1,
1) 若是W1<0xD800或者W1>0xDFFF,則W1表示的字符落在BMP上,W1自己即爲Unicode碼位,算法結束。不然,
2) W1和隨後的一個16位整數W2構成一個字符的UTF-16編碼,檢查是否W1落在0xD800~0xDBFF之間,而且W2落在0xDC00~0xDFFF之間,不然編碼錯誤,算法結束。否則,
3) 去掉W1開頭的110110,W2開頭的110111,餘下的20位二進制數再加上0x10000就是碼位。
UTF-16編碼是雙字節編碼,這種編碼在內存中存放、經過網絡傳送、或者存儲到磁盤文件中時,都只能以字節爲單位,這就存在一個字節順序的問題。對於多字節數據,計算機硬件系統從來就存在兩種字節順序,一種稱爲「高位在前,低位在後」(big-endian),即最高位的數據字節先存放或傳送,而後纔是低位的字節。例如,一個兩字節的整數0x1234,其字節順序是先0x12,而後纔是0x34。另一種字節順序是「低位在前,高位在後」(little-endian),最低位的數據字節先存放或傳送,而後纔是高位的字節。對於0x1234,其字節順序是0x34在前面,而後纔是0x12。IBM PC機的字節順序是little-endian,學過彙編語言的讀者對此應該是很熟悉的。
爲便於各類硬件系統的處理,UTF-16編碼的字符可使用任何一種字節次序,可是在網絡上傳送或存儲時,必須指明所使用的字節次序。方法是在字節流開頭使用一個稱爲字節序標誌(Byte Order Mark, BOM)的代碼0xFEFF,對於big-endian次序,開頭兩個字節必須依次爲0xFE和0xFF;對於little-endian次序,則依次爲0xFF和0xFE。BOM不只指出字節次序,同時也能夠順便做爲UTF-16的編碼標誌:若是一個文字文件的開頭兩字節是BOM,則這個文件的文字編碼就是UTF-16。
如前所述,ASCII字符在BMP第0行中,它們的UTF-16編碼即爲其碼位。因爲行號是0,因此碼位兩個字節中第一個字節一定是0x00,第二個字節與ASCII相同。例如,字母A的ASCII碼是0x41,對應的UTF-16編碼爲0x0041。字節0x00將會帶來麻煩。咱們知道,C語言的字符串是以0x00做爲字符串結束標誌的,目前大量的應用程序是用C語言編寫的,若是將UTF-16編碼的字符串交給這些程序處理,則其中出現的0字節就會致使錯誤。改寫全部的應用程序顯然是不現實的,可行的辦法是採起另外一種編碼方法,這就是UTF-8。
UTF-8採起不定長的編碼方法,ASCII字符的編碼只用一個字節,而且其編碼就是原來的ASCII碼,其它字符的編碼從2個字節到4個字節不等。每一個字符編碼的第一字節指出該字符編碼的字節數:其中第一個二進制數0的左邊有多少個1,該字符編碼就有多少個字節;若是第一位就是0,則該字符編碼只有一個字節。例如,某個字符編碼第一字節是0xxxxxxx(x表示任意的二進制數),則該字符的UTF-8編碼只有一個字節。若某個字符編碼第一字節爲110xxxxx,則該字符的編碼將有兩個字節。對於兩個字節及其以上的UFT-8編碼,從第二字節開始,每一個字節開頭兩個二進制數都是10。
RFC3629描述了詳細的編碼算法。如表1所示,其中最左邊一欄是字符在ISO/IEC 1064六、Unicode編碼空間中的碼位,用16進制數表示爲0xAAAABBCC的形式,其中AAAA是平面號,BB是行號,CC是列號。表中每一行是一個Unicode碼位的範圍,該範圍內碼位佔用的二進制位數列在第3欄中,只需將字符碼位轉換爲二進制數,位數按第3欄的數目,而後將第2欄中的每一個x用碼位中對應的二進制數填上去,就獲得對應的UTF-8編碼。
例如,字符「A」的碼位是0x00000041,落在表1第一行範圍內,所以轉換爲7位二進制數1000001,將這些二進制數依次填到第二欄的每一個x中,就獲得其UTF-8編碼01000001,這其實就是字符「A」的ASCII碼。
又如,漢字「碼」的碼位是0x00007801,位於第三行範圍內,轉換爲16位二進制數0111100000000001,由第二欄知,對應的UTF-8編碼爲3個字節:11100111 10100000 10000001。
表1:UTF-8編碼
字符碼位(16進制) |
UTF-8編碼(二進制) |
二進制位數 |
0x00000000 – 0x0000007F |
0xxxxxxx |
7 |
0x00000080 – 0x000007FF |
110xxxxx 10xxxxxx |
11 |
0x00000800 – 0x0000FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
16 |
0x00010000 – 0x0010FFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
21 |
注意碼位0x0000D800~0x0000DFFF是留給UTF-16使用的替換區,這個範圍內的代碼不容許轉換爲UTF-8。若是是將UTF-16轉換爲UTF-8,則必須還原爲Unicode的碼位而後再按表1進行轉換。
UTF-8將全部的ASCII字符仍編碼爲ASCII碼,而且只有ASCII的空字符的編碼是0x00,其它任何字符的任何字節都不是0,這就解決了C字符串的問題,使得這種編碼的字符串能夠被現有的應用程序使用。
因爲CJK統一漢字的碼位在0x00003000~0x00009FFF之間,因此每一個漢字的UTF-8編碼將是3個字節。
UTF-8的編碼逆算法不難實現,按表1反過來計算便可。可是要注意計算結果是否與表1第一欄指定的範圍一致,例如,將兩個字節0xC0,0x80按表1計算對應的碼位,因爲這兩個字節用二進制表示是11000000 10000000,因而按表1第二行轉換爲0x0000。這是錯誤的,由於0x0000並不在第二行的碼位範圍內。事實上,第二行最小的碼位是0x0080,對應的UTF-8編碼是0xC二、0x80,所以任何UTF-8編碼字節都不多是0xC0或0xC1。經過相似的計算還能夠知道,0xF5~0xFF也不會出如今任何合法的UTF-8編碼字節中。
不像UTF-16,UTF-8編碼是以字節爲單位的,因此不存在字節次序的問題。可是能夠利用BOM做爲編碼標識,以便應用程序識別出當前字節流是UTF-8編碼。作法相似於UTF-16,在編碼字節流的開頭放置BOM,但如今不是0xFEFF,而是BOM對應的UTF-8編碼,它是三個字節:0xEF, 0xBB, 0xBF。
UTF-32用4個字節(32位二進制數)表示一個字符,直接使用ISO/IEC 10646和Unicode所定義的碼位做爲編碼,沒必要進行任何變換。可是,BMP中的替換區0xD800~0xDFFF中的碼位不表明任何字符,所以排除在外,另外,UTF-32屬於Unicode編碼,而Unicode只使用碼位不超過0x10FFFF的字符,所以UTF-32的編碼不能大於0x10FFFF。
UTF-32一樣用BOM指出編碼的字節次序,只不過其BOM是4個字節。若「高位在前,低位在後」,則BOM的4個字節依次爲0x00、0x00、0xFE和0xFF;若「低位在前,高位在後」,則依次爲0xFF、0xFE、0x00和0x00。前者又稱爲UTF-32BE(BE表示Big-Endian),後者又稱爲UTF-32LE(LE表示Little-Endian)。
UCS是「全球字符集」(Universal Character Set)的英文縮寫,表示ISO/IEC 10646和Unicode所定義的所有字符。
UCS-2和UCS-4相似於UTF-16和UTF-32,是UCS的兩種編碼方式。
UCS-2是兩個字節的編碼,只能對BMP中的字符編碼,而且其編碼就是這些字符在BMP平面內的碼位。所以,對於BMP中的字符,UCS-2和UTF-16實際上是同樣的,可是對於其它平面的字符,UCS-2沒法表示,而UTF-16能夠用替換的方式表示。
UCS-4是4個字節編碼,其編碼就是字符碼位。UCS-4與UTF-32是同樣的,二者幾乎能夠認爲是同義詞,只不過UCS-4沒有限制編碼不能超過0x10FFFF。
綜上所述,UCS-4和UTF-32直接將字符碼位做爲字符編碼,雖然直截了當,可是每一個字符要佔用4個字節,浪費存儲空間。UTF-16和UCS-2每一個字符僅用兩個字節,對於BMP中的字符,二者也是直接將碼位做爲字符編碼。UTF-8採用的是不定長編碼,字符編碼字節數不固定,編碼較爲複雜,但UTF-8使得ASCII字符的編碼仍爲一個字節的ASCII碼,這對於網絡上的應用是很重要的,由於不少網絡協議在處理URI、域名、郵件地址等重要信息時,是隻認ASCII字符的。
當咱們只須要說起字符碼位,沒必要涉及編碼時,咱們能夠用符號「U+」接上表示碼位的十六進制數來表示這個字符,例如,U+7801表示漢字「碼」,U+41表示英文字母「A」。
計算機文獻中常用16進制數是由於16進制數和二進制數的轉換很是方便,其規則是:4位二進制數對應一位16進制數。附表一列出全部4位二進制數對應的16進制數和10進制數。
附表一:
二進制 |
16進制 |
10進制 |
0000 |
0 |
0 |
0001 |
1 |
1 |
0010 |
2 |
2 |
0011 |
3 |
3 |
0100 |
4 |
4 |
0101 |
5 |
5 |
0110 |
6 |
6 |
0111 |
7 |
7 |
1000 |
8 |
8 |
1001 |
9 |
9 |
1010 |
A |
10 |
1011 |
B |
11 |
1100 |
C |
12 |
1101 |
D |
13 |
1110 |
E |
14 |
1111 |
F |
15 |
例如,8位二進制數10110101轉換16進制時,由上表可查出1011對應B,0101對應5,所以轉換的結果是16進制數B5。
16進制轉換爲10進制則只需計算加權和。設某個16進制數爲,其中諸爲0..F之間的某個16進制數,表示0..15的數值,則對應的10進制數爲
例如,16進制數AE,轉換爲10進制數即爲。
附表二是常見的2的整數次冪及其對應16進制數和10進制數,在計算編碼時常常會用到這些常數。
附表二:
2的n次方 |
10進制 |
16進制 |
n=1 |
2 |
2 |
n=2 |
4 |
4 |
n=3 |
8 |
8 |
n=4 |
16 |
10 |
n=5 |
32 |
20 |
n=6 |
64 |
40 |
n=7 |
128 |
80 |
n=8 |
256 |
100 |
n=9 |
512 |
200 |
n=10 |
1024 |
400 |
n=16 |
65536 |
10000 |
Windows的「記事本」可用來輸入文字,並保存爲純文本文件。保存時可選擇字符編碼,其選擇項及含意是:
ANSI:文本中的英文字符用ASCII,漢字用GBK
Unicode: 全部字符都用UTF-16編碼,字節順序爲低位在前,自動加BOM
Unicode big endian:全部字符都用UTF-16編碼,字節順序爲高位在前,自動加BOM
UTF-8:全部字符都用UTF-8編碼,自動加BOM
而後咱們能夠利用一些軟件檢查文件中的編碼。例如利用Ultra Edit,這個軟件能夠查看文件中的二進制內容(用16進制數表示),還能夠直接編輯文件中的二進制數。
也能夠利用Windows自帶的一個DOS程序Debug,如下是操做方法,假定你的文件存放路徑及文件名爲d:\code\test.txt:
l 啓動DOS窗口
l 在DOS窗口中輸入cd\
l 在DOS窗口中輸入debug
l debug啓動後,在光標位置輸入n d:\code\test.txt。這個命令的做用是指定文件名,注意這是DOS程序,文件名和目錄名不能用漢字,且不能超過8個字符。
l 輸入L 3000:0。這個命令的做用是將指定的文件裝入內存,3000:0是內存地址。
l 輸入D 3000:0。這個命令的做用是顯示內存中的數據,你將看到相似以下的數據:
3000:0000 C2 EB B0 A1 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
3000:0070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
-
其中左邊一欄是內存地址,中間一欄是文件的二進制數據,用16進製表示,每兩個16進制數字對應一個字節的數據。右邊一欄顯示這些數據對應的字符,但只能顯示ASCII字符,沒法顯示的字符用「.」代替。
l 最後,輸入q,這個命令將退出程序,返回DOS窗口。
l 輸入exit關閉窗口。
注意GBK是兩字節的編碼,Windows存儲GBK編碼的字節次序是高位在前、低位在後。UTF-16編碼的文件,開頭有兩字節的BOM可說明字節次序,UTF-8沒有字節次序的問題,但文件開頭有3個字節的BOM。
debug也能夠修改內存數據並存盤。這個程序原本是用來調試彙編程序的,咱們只是利用其部分功能。