談一談字符編碼的事

談一談字符編碼的事

   字符編碼是程序員最頭疼的問題,有一句話可以形容字符編碼的重要性。“大家都統一用UTF,不然最後怎麼死的都不知道”。因爲文字是用戶日常交流的基礎,而字符編碼是文字在計算機系統中的表示,如果在一個系統中字符編碼都沒有確定,那麼最後很容易出現亂碼問題。對於亂碼問題,當涉及到的模塊增多,系統間的交互增多,解決起來就變得異常困難。字符編碼無小事,不要認爲你寫的程序很小,不涉及到多個模塊間的交互,就可以避免字符編碼問題。這個想法是完全的錯誤。編碼問題非常基礎,如一個字符串拷貝函數就會涉及,估計沒有人會不用吧。那麼當你使用字符處理函數時,有考慮過內存裏面的0101到底採用是什麼編碼嗎?經過操作的字符串最後還能否會被轉化爲可讀的文字。下面我們談一談字符編碼的問題。

1.       什麼是字符編碼

字符編碼,指對人們使用的文字中的每一個符號,用二進制01組成的串進行表示。不同的符號對應的01串不同,每個符號對應的01串就是該符號的編碼。

2.       字符編碼中的常見概念

A.      ASCII

ASCIIAmerican Standard Code for Information Interchange,美國信息互換標準代碼,ASCⅡ)是基於拉丁字母的一套電腦編碼。它主要用於顯示現代英語和其他西歐語言,它採用單字節編碼

ASCII共定義了128個字符,其中33個字符無法顯示,在33個字符之外的是95個可顯示的字符,包含用鍵盤敲下空白鍵所產生的空白字符也是1個可顯示字符(顯示爲空白)。可以在這個網站查詢到ASCII的對照表http://ascii.911cha.com/

補充:ASCII第一次以規範標準的形態發表是在1967年,最後一次更新則是在1986年,至今爲止共定義了128個字符,其中33個字符無法顯示(這是以現今操作系統爲依據,但在DOS模式下可顯示出一些諸如笑臉、撲克牌花式等8-bit符號),且這33個字符多數都是已廢的控制字符,控制字符的用途主要是用來操控已經處理過的文字。

B.      GB2312

ASCII碼是單字節編碼方式,對於漢字有10萬多個字符的語言,單字節編碼僅能表示256個字符。

GB 2312GB 2312-80是一個簡體中文字符集的中國國家標準,全稱爲《信息交換用漢字編碼字符集·基本集》,又稱爲GB0,由中國國家標準總局發佈,198151日實施。GB2312編碼通行於中國大陸。

具體的編碼規則就不研究了,開發時不用去了解具體的編碼規則。我們只要知道每個漢字及符號以兩個字節來表示。同時,GB 2312標準共收錄6763個漢字,其中一級漢字3755個,二級漢字3008個,可見生僻字不能顯示。

補充:GB 2312還收錄了包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西裏爾字母在內的682個全角字符。GB 2312的出現,基本滿足了漢字的計算機處理需要,它所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率。新加坡等地也採用此編碼。

C.       ANSI編碼

因爲ASCII碼的侷限性,不同的國家和地區制定了不同的擴展標準,由此產生了 GB2312, BIG5, JIS等各自的編碼標準。在簡體中文系統下,ANSI編碼代表 GB2312編碼,在日文操作系統下,ANSI 編碼代表 JIS 編碼。 不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬於兩種語言的文字,存儲在同一段 ANSI編碼的文本中。

D.      Unicode編碼

Unicode也是一種字符編碼方法,不過它是由國際組織設計,可以容納全世界所有語言文字的編碼方案。Unicode的學名是"Universal Multiple-Octet Coded Character Set",簡稱爲UCSUCS可以看作是"Unicode Character Set"的縮寫。

UCS有兩種格式:UCS-2UCS-4。顧名思義,UCS-2就是用兩個字節編碼,UCS-4就是用4個字節(實際上只用了31位,最高位必須爲0)編碼。

UCS規定了怎麼用多個字節表示各種文字。怎樣存儲、傳輸這些編碼,是由UTF(UCS Transformation Format)規範規定的,常見的UTF規範包括UTF-8UTF-7UTF-16

E.       UTF編碼

UTF-8就是以8位爲單元對UCS進行編碼,它是一種變長的編碼方式。它可以使用1-4個字節表示一個符號,根據不同的符號而變化字節長度。從UCS-2UTF-8的編碼方式如下:

例如“漢”字的Unicode編碼是6C496C490800-FFFF之間,所以肯定要用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89

UTF-1616位爲單元對UCS進行編碼。對於小於0x10000UCS碼,UTF-16編碼就等於UCS碼對應的16位無符號整數。對於不小於0x10000UCS碼,定義了一個算法。不過由於實際使用的UCS2,或者UCS4BMP必然小於0x10000,所以就目前而言,可以認爲UTF-16UCS-2基本相同。UCS-2只是一個編碼方案,UTF-16卻要用於實際的傳輸,所以就不得不考慮字節序的問題。

F.       字節序(Little endianBig endian)和BOM

UTF-8以字節爲編碼單元,沒有字節序的問題。UTF-16以兩個字節爲編碼單元。

UTF-16使用的兩個字節存在一個順序的問題。比如“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。那麼當我們收到UTF-16字節流“594E”,那麼這是“奎”還是“乙”?

字節序就是規定UTF-16所用兩個字節的順序。以漢字”嚴“爲例,Unicode碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在後,就是Big endian方式;25在前,4E在後,就是Little endian方式。

UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的編碼是FEFF。而FEFFUCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸字節流前,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"

這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM

UTF-8不需要BOM來表明字節順序,但可以用BOM來區分編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"UTF-8編碼是EF BB BF(可用前面介紹的編碼方法計算)。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。

Windows就是使用BOM來標記文本文件的編碼方式的。Windows中的Unicode字符一般指UCS2UTF16-LE編碼。

3.       編碼間如何轉化

A.      ANSI編碼和UCS-2 LE編碼

我們以漢字“你好”爲例進行說明,先看一下“你好”的各種編碼:

漢字

ANSI

UTF-8

UCS-2 BE

UCS-2 LE

你好

C4 E3 BA C3

EF BB BFBOM區分編碼)

E4 BD A0 E5 A5 BD

FE FFBOM表示字節序)

4F 60 59 7D

FF FEBOM表示字節序)

60 4F 7D 59

 

輸出編碼程序:

1)  ANSI編碼

char* pchar = "你好";

 

int len = strlen(pchar);

for (int i=0; i<len; i++ )

{

    char each = pchar[i];

    printf("Hex: %x\r\n",each );

}

printf("===========================\r\n");

2)  UCS-2 LE

wchar_t* pwchar = L"你好";

int wlen = wcslen( pwchar );

int bytelen = wlen * sizeof( wchar_t );

char* point = (char*) pwchar;//強制轉化爲char*,按照一個字節一個字節的方式輸出內存中的內容

for (int i=0; i<bytelen; i++ )

{

    char each = point[i];

    printf("Hex: %x\r\n",each );

}

printf("===========================\r\n");

B.      ANSIUCS-2 LE之間的轉換

1)  Ansi轉化爲Unicode

MultiByteToWideChar返回的是字符數,需要乘以sizeof(wchar_t)轉化爲內存大小。返回的字符數包含了字符串的結尾字符’\0’需要的一個字符。

所以輸出結果:

2)  Unicode轉化爲Ansi

WideCharToMultiByte返回的是需要的字節數,可以直接分配內存,返回的字節數包含目標ANSI’\0’爲結尾的字節數。

所以輸出結果:

C.       UTF-8UCS-2 LE之間的轉換

1)  Unicode轉化爲UTF-8

WideCharToMultiByte返回的是需要的字節數,可以直接分配內存,返回的字節數包含目標UTF-8’\0’爲結尾的字節數。

所以輸出結果:

UTF-8’\0’以單字節表示

4.       不同字符編碼下字符串如何結尾?

   字符串都以\0結束,但是不同編碼存儲\0所用的字節數不同。通過上面的實驗可以看到,ANSIUTF-8採用單字節存儲\0Unicode採用雙字節存儲\0

5.       URLENCODE是字符編碼嗎?

   URLENCODE與前面的ANSIUnicode,編碼不同。URLENCODE是對字符經過ANSIUnicode編碼後的結果,再次轉化便於URL傳輸。

比如:

“中文” -> GB2312編碼爲D6D0CEC4,經過Encode -> %D6%D0%CE%C4

“中文”-> UTF-8編碼爲E4B8ADE69687,經過Encode -> %E4%B8%AD%E6%96%87



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章