轉載:http://techmi.cn/archives/你不知道的字符集和編碼.htmlhtml
http://news.cnblogs.com/n/139987/node
其實即便是如今對編碼仍是很糊,緣由不少概念的混亂,不明晰致使的。python
常說的字符集和編碼區別,其實就是編碼字符集和字符集編碼的區別,其實,單單若是隻是說字符集,沒有任何編碼的概念的話,那麼字符集其實僅僅是一個簡單的字符的集合,或者說是一個抽象的字符的集合,包括文字,符號等等,不參與任何存儲形式,只是存在這麼各類各樣標準的字符的集合。linux
若是僅僅是抽象的字符集,咱們是無需拿出討論的,由於沒有任何異議,通俗易懂,而常說的字符集指的編碼字符集,好比常見的 unicode、ascii、gb23十二、gbk等,這些咱們常稱作爲字符集(實際上是編碼字符集),這些字符集,好比unicode其實本質上是已經「編碼」過的字符集,即每一個字符都有惟一的整數編號,每一個字符都有本身特有的編號,同一個字符在不一樣編碼字符集中編號也會不一樣,固然不少編碼字符集都是ascll的超集,因此ascll字符集的編號與不少編碼字符集中編號都同樣,好比英文字母「A」,在ASCII及Unicode及GB2312中,均是第0×41個字符,說到這裏朋友必定注意到了我上面再描述「 unicode其實本質上是已經「編碼」過的字符集」中的「編碼」二字加了雙引號,我要強調的是這裏的「編碼」並非真的我下面要說的編碼,這裏只是爲每一個字符編了一個對應的編號,可是咱們仍是習慣專業的稱呼爲「編碼字符集」面試
咱們常常說「文章採用的是utf-8編碼方式」shell
我對於這個編碼方式的意義,我的理解是 將一個字符的整數編號用一個什麼二進制的整數值來對應並在計算機存儲。這和上面說的編碼字符集中的「編碼」千差萬別,這裏咱們稱之爲「字符集編碼」,即咱們常說的編碼vim
說到這裏,不少人會以爲那麼unicode和utf-8的區別在哪裏?既然上文說到unicode是編碼字符集,那麼utf-8又是什麼?就是常說的編碼?sublime-text
「文章採用的是utf-8編碼方式」,我的以爲準確的說法是「文章採用的是基於unicode編碼字符集的utf-8的編碼方案」,即數組
即unicode自己做爲編碼字符集沒有任何存儲形式,只是一個編號和字符對應的表而已,如何在計算機存儲?你可能想到了乾脆直接把編號看成二進制數值來直接存儲,那麼爲何不這麼作呢?這也算是一種字符集編碼方案,就是基於unicode編碼字符集的utf-32編碼方案,那麼有沒有更加智能一點的編碼方案呢?爲何會沒有呢?那就是utf-八、utf-16等等, 等等,在我解釋爲什麼要用utf-8編碼方案的時候,我必須說明一件事情:以下瀏覽器
我在上一篇文章《你不知道的 頁面編碼,瀏覽器選擇編碼,get,post各類亂碼由來》中說過:「如何查看中文字符的十六進制字符串?方法:BitConverter.ToString(System.Text.Encoding.UTF8.GetBytes(「阿道夫」));」 請注意我能夠改成「System.Text.Encoding.Unicode.GetBytes」 以下圖是vs2013 Encoding鍵入「.」後的智能提示
(列表過長,用兩幅圖分別截圖)
上圖有兩個疑問:
一、若是說unicode是編碼字符集,爲什麼會出如今和utf-8這種編碼方案並列的列表中?
二、ASCII或者gb2312都是編碼字符集爲什麼也會出如今和utf-8這種編碼方案並列的列表中?
咱們假設有兩個猜想:
一、此處的unicode並非真正的unicode編碼字符集,可能只是一種和unicode編碼字符集關係很是緊密的一種編碼方案
二、ASCII或者gb2312(其實就是圖中的Default,即操做系統當前的編碼,國內通常爲gb2312)是編碼字符集沒有錯,可是對於ASCII或者gb2312都只有惟一一種編碼,那麼我稱呼它們爲ASCII編碼或者GB2312編碼也沒有問題,既然這樣,那我把ascii和gb2312加入和utf-8這種編碼方案並列的列表中也理所固然?
個人兩個假設,很快獲得論證
一、在Encoding 的元數據看到:
1
2
3
4
5
6
7
|
//
// 摘要:
// 獲取使用 Little-Endian 字節順序的 UTF-16 格式的編碼。
//
// 返回結果:
// 使用 Little-Endian 字節順序的 UTF-16 格式的編碼。
public static Encoding Unicode { get; }
|
這裏解釋在這裏的unicode其實本質上「獲取使用 Little-Endian 字節順序的 UTF-16 格式的編碼」,即便基於unicode編碼字符集的utf-16編碼方案,相似的有BigEndianUnicode(獲取使用 Big Endian 字節順序的 UTF-16 格式的編碼)
二、通常的ASCII或者gb2312,咱們能夠稱呼爲ASCII字符集也能夠稱呼爲ASCII編碼,只是意義不一樣而已,由於對於ASCII編碼字符集或者gb2312編碼字符集都只有惟一一種編碼,就是ASCII編碼和GB2312編碼,那麼列表中顯示的ASCII和GB2312指的不是編碼字符集而是ASCII和GB2312的編碼方案,我想正是這種緣由,纔在不少時候,無論是字符集賦值仍是編碼方案賦值均可以直接用gb2312或者ascii,好比:
Encoding gb2312 = Encoding.GetEncoding(「gb2312″);
Response.ContentEncoding = gb2312;//編碼
Response.Charset=」gb2312″;//字符集
總結下的說:
就是unicode是字符集,不是編碼!可是ascii(gb2312)是字符集,這個說法確定正確,可是我表達爲「ascii編碼」也不能說大錯特錯,可是這種說法讓人誤解,若是必定要說那麼就說「ascii編碼字符集的編碼」
若是理解上面兩個假設的論證道理,那麼咱們繼續討論以前暫停的話題,即「解釋爲什麼要用utf-8等編碼方案(其餘utf編碼方案相似)」
utf-8將很大一部分基於unicode編碼字符集的字符的整數編號做了變換後存儲在計算機中。(引用)以「漢」字爲例,「漢」的Unicode值爲0x6C49,但其編碼爲UTF-8格式後的值爲0xE6B189(注意到變成了三個字節)。對於UTF-16編碼方案,則是對unicode編碼字符集中的前65536個字符編號都不作變換,直接做爲計算機存儲時使用的值(對65536之後的字符,仍然要作變換),例如「漢」字的Unicode編號爲0x6C49,那麼通過UTF-16編碼後存儲在計算機上時,它的表示仍爲0x6C49,對於UTF-32編碼方案,他對全部的Unicode字符均不作變換,直接使用編號存儲,只是這種編碼方案太浪費存儲空間(就連1個字節就能夠搞定的英文字符,它都必須使用4個字節)
既然unicode編碼字符集有如此多的編碼方案,那麼
utf-8,字母數字符號等佔1字節,漢字佔三字節
utf-16,對unicode編碼字符集中的前65536個字符都佔兩個字節
utf-32,所有佔四字節
若是還有人問:
「unicode編碼每一個字符佔幾個字節」,咱們能夠義正詞嚴的說,第一unicode不是編碼!第二每一個字符具體佔多少字節是要看編碼方案!
不少面試題會問:
1
2
3
|
string param = "abc阿道夫";
int length1 = System.Text.Encoding.Unicode.GetBytes(param).Length;//別忘了這裏的unicode本質是utf-16編碼方案
int length2 = param.Length;
|
那麼答案就是12和6了
最後,對於gb2312或者ascii編碼字符集的字符的編號就是直接存儲在計算機中的二進制數,也就是說gb2312和ascii編碼字符集都只有一種編碼方案,由於在gb2312編碼字符集中的ascii字符集部分的編號並無變化(即和ascii編碼字符集中的編碼一致),因此gb2312的ascii部分字符存入計算機的二進制數仍是佔用1個字節,而中文字符存入計算機的二進制數也是該中文字符在gb2312編碼字符集中的編號,該編號通常轉換成二進制數都佔兩個字節,這個過程也就變成了所謂的gb2312編碼
若是上面的改成System.Text.Encoding.Default.GetBytes(param).Length,則值就是9和6了
##################################################################################
編碼問題的例子
在 Windows 自帶的 Notepad(記事本)程序中輸入「聯通」兩個字,保存後再次打開,會發現「聯通」不見了,代之以「��ͨ」的亂碼。這是 Windows 平臺上典型的中文編碼問題。即文件保存的時候是按照 ANSI 編碼(其實就是 GB2312,後面會詳細介紹)保存,打開的時候程序按照 UTF-8方式對內容解釋,因而就出現了亂碼。避免亂碼的方式很簡單,在「文件」菜單中選擇「打開」命令,選擇保存的文件,而後選擇「ANSI」編碼,此時就能看到久違的「聯通」兩個字了。
在 Linux 平臺上若是使用 cat 等命令查看文件中的中文內容時,可能出現亂碼。這也是編碼的問題。簡單的說是文件時按照A編碼保存,可是 cat 命令按照當前 Locale 設定的B編碼去查看,在B和A不兼容的時候就出現了亂碼。
爲何寫這篇文章
中文編碼因爲歷史緣由牽扯到很多標準,在不瞭解的時候感受一頭霧水;但其實理解編碼問題並不須要你深刻了解各個編碼標準,只要你明白了前因後果,瞭解了關鍵的知識點,就能分析和解決平常開發工做中碰到的大部分編碼問題。有感於我看過的資料和文章要麼不夠全面,要麼略顯枯燥,因此經過這篇文章記錄下筆者在平常工做中碰到的中文編碼原理相關問題,目的主要是自我總結,若是能給讀者提供一些幫助那就算是意外之喜了。因爲嚴謹的編碼標準對我來講是無趣的,枯燥的,難以記憶的,本文嘗試用淺顯易懂的生活語言解釋中文編碼相關的(也可能不相關的)一些問題,這也是爲何取名雜談的緣由。本文確定存在不規範不全面的地方,我會在參考資料裏給出官方文檔的連接,也歡迎讀者在評論中提出更好的表達方式&指出錯誤,不勝感激。
對編碼問題的理解我認爲分爲三個層次,第一個層次:概念,知道各個編碼標準的應用場景,瞭解之間的差別,能分析和解決常見的一些編碼問題。第二個層次:標準,掌握編碼的細節,如編碼範圍,編碼轉換規則,知道這些就能自行開發編碼轉換工具。第三個層次,使用,瞭解中文的編碼二進制存儲,在程序開發過程當中選擇合理的編碼並處理中文。爲了不讓讀者陷入編碼標準的黑洞沒法脫身(不相信?看看 unicode 的規範就明白個人意思了),同時因爲編碼查詢&轉換工具等都有現成工具可使用,本文只涉及第一個層次,不涉及第二層次,在第三層次上會作一些嘗試。在本文的最後提供了相關連接供對標準細節感興趣的同窗繼續學習。最後,本文不涉及具體軟件的亂碼問題解決,如 ssh,shell,vim,screen 等,這些話題留給劍豪同窗專文闡述。
一切都是由於電腦不識字
電腦很聰明,能夠幫咱們作不少事情,最開始主要是科學計算,這也是爲何電腦別名計算機。電腦又很笨,在她的腦子裏只有數字,即全部的數據在存儲和運算時都要使用二進制數表示。這在最初電腦主要用來處理大量複雜的科學計算時不是什麼大問題,可是當電腦逐步走入普通人的生活時,狀況開始變糟了。辦公自動化等領域最主要的需求就是文字處理,電腦如何來表示文字呢?這個問題固然難不倒聰明的計算機科學家們,用數字來表明字符唄。這就是「編碼」。
英文的終極解決方案:ASCII
每一個人均可以約定本身的一套編碼,只要使用方之間瞭解就 ok 了。好比說咱倆約定0×10表示a,0×11表示b。在一開始也的確是這樣的,出現了各式各樣的編碼。這樣有兩個問題:1. 各個編碼的字符集不同,有的多,有的少。2. 相同字符的編碼也不同。你這裏a是0×10,他那裏a多是0×30。因而你保存的文件他就不能直接用,必需要轉換編碼。隨着溝通範圍的擴大,採用不一樣編碼的人們互相通訊就亂套了,這就是咱們常說的:雞同鴨講。若是要避免這種混亂,那麼你們就必須使用相同的編碼規則,因而美國有關的標準化組織就出臺了 ASCII(American Standard Code for Information Interchange)編碼,統一規定了英文經常使用符號用哪些二進制數來表示。ASCII 是標準的單字節字符編碼方案,用於基於文本的數據。
ASCII 最初是美國國家標準,供不一樣計算機在相互通訊時用做共同遵照的西文字符編碼標準,已被國際標準化組織(International Organization for Standardization, ISO)定爲國際標準,稱爲 ISO 646 標準。適用於全部拉丁文字字母。ASCII 碼使用指定的 7 位或 8 位二進制數組合來表示 128 或 256 種可能的字符。標準 ASCII 碼也叫基礎 ASCII 碼,使用 7 位二進制數來表示全部的大寫和小寫字母,數字 0 到九、標點符號, 以及在美式英語中使用的特殊控制字符。而最高位爲 1 的另 128 個字符(80H—FFH)被稱爲「擴展 ASCII」,通常用來存放英文的製表符、部分音標字符等等的一些其它符號。
其中:0~31及127(共33個)是控制字符或通訊專用字符(其他爲可顯示字符),32~126(共 95 個)是字符(32是空格),其中 48~57爲 0 到 9 十個阿拉伯數字,65~90爲 26 個大寫英文字母,97~122號爲 26 個小寫英文字母,其他爲一些標點符號、運算符號等。
如今全部使用英文的電腦終於能夠用同一種編碼來交流了。理解了 ASCII 編碼,其餘字母型的語言編碼方案就舉一反三了。
一波三折的中文編碼
第一次嘗試:GB2312
ASCII 這種字符編碼規則顯然用來處理英文沒有什麼問題,它的出現極大的促進了信息在西方尤爲是美國的傳播和交流。可是對於中文,經常使用漢字就有 6000 以上,ASCII 單字節編碼顯然是不夠用。爲了粉碎美帝國主義經過編碼限制中國人民使用電腦的無恥陰謀,中國國家標準總局發佈了 GB2312 碼即中華人民共和國國家漢字信息交換用編碼,全稱《信息交換用漢字編碼字符集——基本集》,1981年 5 月 1 日實施,通行於大陸。GB2312字符集中除經常使用簡體漢字字符外還包括希臘字母、日文平假名及片假名字母、俄語西裏爾字母等字符,未收錄繁體中文漢字和一些生僻字。 EUC-CN 能夠理解爲 GB2312 的別名,和 GB2312 徹底相同。
GB2312是基於區位碼設計的,在區位碼的區號和位號上分別加上 A0H 就獲得了 GB2312 編碼。這裏第一次提到了「區位碼」,我就連帶把下面這幾個讓人摸不到頭腦的 XX 碼一鍋端了吧:
區位碼,國標碼,交換碼,內碼,外碼
區位碼:就是把中文經常使用的符號,數字,漢字等分門別類進行編碼。區位碼把編碼表分爲 94 個區,每一個區對應 94 個位,每一個位置就放一個字符(漢字,符號,數字都屬於字符)。這樣每一個字符的區號和位號組合起來就成爲該漢字的區位碼。區位碼通常用 10 進制數來表示,如 4907 就表示 49 區 7 位,對應的字符是「學」。區位碼中 01-09區是符號、數字區,16-87區是漢字區,10-15和 88-94是未定義的空白區。它將收錄的漢字分紅兩級:第一級是經常使用漢字計 3755 個,置於 16-55區,按漢語拼音字母/筆形順序排列;第二級漢字是次經常使用漢字計 3008 個,置於 56-87區,按部首/筆畫順序排列。在網上搜索「區位碼查詢系統」能夠很方便的找到漢字和對應區位碼轉換的工具。爲了不廣告嫌疑和死鏈,這裏就不舉例了。
國標碼: 區位碼沒法用於漢字通訊,由於它可能與通訊使用的控制碼(00H~1FH)(即0~31,還記得 ASCII 碼特殊字符的範圍嗎?)發生衝突。因而 ISO2022 規定每一個漢字的區號和位號必須分別加上 32(即二進制數 00100000,16進制 20H),獲得對應的國標交換碼,簡稱國標碼,交換碼,所以,「學」字的國標交換碼計算爲:
00110001 00000111 + 00100000 00100000 ------------------- 01010001 00100111
用十六進制數表示即爲 5127H。
交換碼:即國標交換碼的簡稱,等同上面說的國標碼。
內碼:因爲文本中一般混合使用漢字和西文字符,漢字信息若是不予以特別標識,就會與單字節的 ASCII 碼混淆。此問題的解決方法之一是將一個漢字當作是兩個擴展 ASCII 碼,使表示 GB2312 漢字的兩個字節的最高位都爲1。即國標碼加上 128(即二進制數 10000000,16進制 80H)這種高位爲 1 的雙字節漢字編碼即爲 GB2312 漢字的機內碼,簡稱爲內碼。20H+80H=A0H。這也就是常說的在區位碼的區號和位號上分別加上 A0H 就獲得了 GB2312 編碼的由來。
00110001 00000111 + 10100000 10100000 ------------------- 11010001 10100111
用十六進制數表示即爲 D1A7H。
外碼:機外碼的簡稱,就是漢字輸入碼,是爲了經過鍵盤字符把漢字輸入計算機而設計的一種編碼。 英文輸入時,相輸入什麼字符便按什麼鍵,外碼和內碼一致。漢字輸入時,可能要按幾個鍵才能輸入一個漢字。 漢字輸入方案有成百上千個,可是這千差萬別的外碼輸入進計算機後都會轉換成統一的內碼。
最後總結一下上面的概念。中國國家標準總局把中文經常使用字符編碼爲 94 個區,每一個區對應 94 個位,每一個字符的區號和位號組合起來就是該字符的區位碼, 區位碼用 10 進制數來表示,如 4907 就表示 49 區 7 位,對應的字符是「學」。 因爲區位碼的取值範圍與通訊使用的控制碼(00H~1FH)(即0~31)發生衝突。每一個漢字的區號和位號分別加上 32(即 16 進制 20H)獲得國標碼,交換碼。「學」的國標碼爲 5127H。因爲文本中一般混合使用漢字和西文字符,爲了讓漢字信息不會與單字節的 ASCII 碼混淆,將一個漢字當作是兩個擴展 ASCII 碼,即漢字的兩個字節的最高位置爲1,獲得的編碼爲 GB2312 漢字的內碼。「學」的內碼爲 D1A7H。不管你使用什麼輸入法,經過什麼樣的按鍵組合把「學」輸入計算機,「學」在使用 GB2312(以及兼容 GB2312)編碼的計算機裏的內碼都是 D1A7H。
第二次嘗試:GBK
GB2312的出現基本知足了漢字的計算機處理須要,但因爲上面提到未收錄繁體字和生僻字,從而不能處理人名、古漢語等方面出現的罕用字,這致使了 1995 年《漢字編碼擴展規範》(GBK)的出現。GBK 編碼是 GB2312 編碼的超集,向下徹底兼容 GB2312,兼容的含義是不只字符兼容,並且相同字符的編碼也相同,同時在字彙一級支持 ISO/IEC10646—1和 GB 13000—1的所有中、日、韓(CJK)漢字,共計 20902 字。GBK 還收錄了 GB2312 不包含的漢字部首符號、豎排標點符號等字符。CP936和 GBK 的有些許差異,絕大多數狀況下能夠把 CP936 看成 GBK 的別名。
第三次嘗試:GB18030
GB18030編碼向下兼容 GBK 和 GB2312。GB18030收錄了全部 Unicode3.1 中的字符,包括中國少數民族字符,GBK 不支持的韓文字符等等,也能夠說是世界大多民族的文字符號都被收錄在內。GBK 和 GB2312 都是雙字節等寬編碼,若是算上和 ASCII 兼容所支持的單字節,也能夠理解爲是單字節和雙字節混合的變長編碼。GB18030編碼是變長編碼,有單字節、雙字節和四字節三種方式。
其實,這三個標準並不須要死記硬背,只須要了解是根據應用需求不斷擴展編碼範圍便可。從 GB2312 到 GBK 再到 GB18030 收錄的字符愈來愈多便可。萬幸的是一直是向下兼容的,也就是說一個漢字在這三個編碼標準裏的編碼是如出一轍的。這些編碼的共性是變長編碼,單字節 ASCII 兼容,對其餘字符 GB2312 和 GBK 都使用雙字節等寬編碼,只有 GB18030 還有四字節編碼的方式。這些編碼最大的問題是 2 個。1. 因爲低字節的編碼範圍和 ASCII 有重合,因此不能根據一個字節的內容判斷是中文的一部分仍是一個獨立的英文字符。2. 若是有兩個漢字編碼爲 A1A2B1B2,存在 A2B1 也是一個有效漢字編碼的特殊狀況。這樣就不能直接使用標準的字符串匹配函數來判斷一個字符串裏是否包含某一個漢字,而須要先判斷字符邊界而後才能進行字符匹配判斷。
最後,提一個小插曲,上面講的都是大陸推行的漢字編碼標準,使用繁體的中文社羣中最經常使用的電腦漢字字符集標準叫大五碼(Big5),共收錄 13,060箇中文字,其中有二字爲重覆編碼(實在是不該該)。Big5雖普及於中國的臺灣、香港與澳門等繁體中文通行區,但長期以來並不是當地的國家標準,而只是業界標準。倚天中文系統、Windows 等主要系統的字符集都是以 Big5 爲基準,但廠商又各自增刪,衍生成多種不一樣版本。2003年,Big5被收錄到臺灣官方標準的附錄當中,取得了較正式的地位。這個最新版本被稱爲 Big5-2003。
天下歸一 Unicode
看了上面的多箇中文編碼是否是有點頭暈了呢?若是把這個問題放到全世界n多個國家n多語種呢?各國和各地區本身的文字編碼規則互相沖突的狀況全球信息交換帶來了很大的麻煩。
要真正完全解決這個問題,上面介紹的那些經過擴展 ASCII 修修補補的方式已經走不通了,而必須有一個全新的編碼系統,這個系統要能夠將中文、日文、法文、德文……等等全部的文字統一塊兒來考慮,爲每個文字都分配一個單獨的編碼。因而,Unicode 誕生了。Unicode(統一碼、萬國碼、單一碼)爲地球上(之後會包括火星,金星,喵星等)每種語言中的每一個字符設定了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求。在 Unicode 裏,全部的字符被一視同仁,漢字再也不使用「兩個擴展 ASCII」,而是使用「1個 Unicode」來表示,也就是說,全部的文字都按一個字符來處理,它們都有一個惟一的 Unicode 碼。Unicode 用數字0-0x10FFFF 來映射這些字符,最多能夠容納 1114112 個字符,或者說有 1114112 個碼位(碼位就是能夠分配給字符的數字)。
提到 Unicode 不能不提 UCS(通用字符集 Universal Character Set)。UCS 是由 ISO 制定的 ISO 10646(或稱 ISO/IEC 10646)標準所定義的標準字符集。UCS-2用兩個字節編碼,UCS-4用 4 個字節編碼。Unicode 是由 unicode.org 制定的編碼機制,ISO 與 unicode.org 是兩個不一樣的組織, 雖然最初制定了不一樣的標準; 但目標是一致的。因此自從 Unicode 2.0 開始, Unicode 採用了與 ISO 10646-1相同的字庫和字碼, ISO 也承諾 ISO10646 將不會給超出 0x10FFFF 的 UCS-4編碼賦值, 使得二者保持一致。你們簡單認爲 UCS 等同於 Unicode 就能夠了。
在 Unicode 中:漢字「字」對應的數字是 23383。在 Unicode 中,咱們有不少方式將數字 23383 表示成程序中的數據,包括:UTF-八、UTF-1六、UTF-32。UTF 是「UCS Transformation Format」的縮寫,能夠翻譯成 Unicode 字符集轉換格式,即怎樣將 Unicode 定義的數字轉換成程序數據。例如,「漢字」對應的數字是 0x6c49 和 0x5b57,而編碼的程序數據是:
BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97}; // UTF-8編碼 WORD data_utf16[] = {0x6c49, 0x5b57}; // UTF-16編碼 DWORD data_utf32[] = {0x6c495b57}; // UTF-32編碼
這裏用 BYTE、WORD、DWORD 分別表示無符號 8 位整數,無符號 16 位整數和無符號 32 位整數。UTF-八、UTF-1六、UTF-32分別以 BYTE、WORD、DWORD 做爲編碼單位。「漢字」的 UTF-8編碼須要 6 個字節。「漢字」的 UTF-16編碼須要兩個 WORD,大小是 4 個字節。「漢字」的 UTF-32編碼須要兩個 DWORD,大小是 8 個字節。根據字節序的不一樣,UTF-16能夠被實現爲 UTF-16LE 或 UTF-16BE,UTF-32能夠被實現爲 UTF-32LE 或 UTF-32BE。
下面介紹 UTF-八、UTF-1六、UTF-3二、BOM。
UTF-8
UTF-8以字節爲單位對 Unicode 進行編碼。從 Unicode 到 UTF-8的編碼方式以下:
Unicode 編碼(16進制) | UTF-8 字節流(二進制) |
000000 – 00007F | 0xxxxxxx |
000080 – 0007FF | 110xxxxx 10xxxxxx |
000800 – 00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000 – 10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8的特色是對不一樣範圍的字符使用不一樣長度的編碼。對於0×00-0x7F 之間的字符,UTF-8編碼與 ASCII 編碼徹底相同。UTF-8編碼的最大長度是 4 個字節。從上表能夠看出,4字節模板有 21 個x,便可以容納 21 位二進制數字。Unicode 的最大碼位 0x10FFFF 也只有 21 位。總結了一下規律:UTF-8的第一個字節開始的 1 的個數表明了總的編碼字節數,後續字節都是以 10 開始。由上面的規則能夠清晰的看出 UTF-8編碼克服了中文編碼的兩個問題。
例1:「漢」字的 Unicode 編碼是 0x6C49。0x6C49在0×0800-0xFFFF 之間,使用 3 字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將 0x6C49 寫成二進制是:0110 1100 0100 1001, 用這個比特流依次代替模板中的x,獲得:11100110 10110001 10001001,即 E6 B1 89。
例2:Unicode 編碼 0x20C30 在0×010000-0x10FFFF 之間,使用用 4 字節模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。將 0x20C30 寫成 21 位二進制數字(不足 21 位就在前面補0):0 0010 0000 1100 0011 0000,用這個比特流依次代替模板中的x,獲得:11110000 10100000 10110000 10110000,即 F0 A0 B0 B0。
UTF-16
UTF-16編碼以 16 位無符號整數爲單位。咱們把 Unicode 編碼記做U。編碼規則以下:若是U<0×10000,U的 UTF-16編碼就是U對應的 16 位無符號整數(爲書寫簡便,下文將 16 位無符號整數記做 WORD)。中文範圍 4E00-9FBF,因此在 UTF-16編碼裏中文 2 個字節編碼。若是U≥0×10000,咱們先計算U’=U-0×10000,而後將U’寫成二進制形式:yyyy yyyy yyxx xxxx xxxx,U的 UTF-16編碼(二進制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
UTF-32
UTF-32編碼以 32 位無符號整數爲單位。Unicode 的 UTF-32編碼就是其對應的 32 位無符號整數。
字節序
根據字節序(對字節序不太瞭解的同窗請參考 http://en.wikipedia.org/wiki/Endianness)的不一樣,UTF-16能夠被實現爲 UTF-16LE(Little Endian)或 UTF-16BE(Big Endian),UTF-32能夠被實現爲 UTF-32LE 或 UTF-32BE。例如:
Unicode 編碼 | UTF-16LE | UTF-16BE | UTF-32LE | UTF-32BE |
0x006C49 | 49 6C | 6C 49 | 49 6C 00 00 | 00 00 6C 49 |
0x020C30 | 43 D8 30 DC | D8 43 DC 30 | 30 0C 02 00 | 00 02 0C 30 |
那麼,怎麼判斷字節流的字節序呢?Unicode 標準建議用 BOM(Byte Order Mark)來區分字節序,即在傳輸字節流前,先傳輸被做爲 BOM 的字符」零寬無中斷空格」。這個字符的編碼是 FEFF,而反過來的 FFFE(UTF-16)和 FFFE0000(UTF-32)在 Unicode 中都是未定義的碼位,不該該出如今實際傳輸中。下表是各類 UTF 編碼的 BOM:
UTF 編碼 | Byte Order Mark |
UTF-8 | EF BB BF |
UTF-16LE | FF FE |
UTF-16BE | FE FF |
UTF-32LE | FF FE 00 00 |
UTF-32BE | 00 00 FE FF |
總結一下,ISO 與 unicode.org 都敏銳的意識到只有爲世界上每種語言中的每一個字符設定統一而且惟一的二進制編碼才能完全解決計算機世界信息交流中編碼衝突的問題。由此誕生了 UCS 和 Unicode,而這兩個規範是一致的。在 Unicode 裏,全部的字符被一視同仁,也就是說,全部的文字都按一個字符來處理,它們都有一個惟一的 Unicode 碼。UTF-八、UTF-1六、UTF-32分別定義了怎樣將 Unicode 定義的數字轉換成程序數據。UTF-8以字節爲單位對 Unicode 進行編碼,一個英文字符佔 1 個字節,漢字佔 3 個字節;UTF-16以 16 位無符號整數爲單位對 Unicode 進行編碼,中文英文都佔 2 個字節;UTF-32以 32 位無符號整數爲單位對 Unicode 進行編碼,中文英文都佔 4 個字節。能夠在 http://www.unicode.org/charts/unihan.html 查看漢字的 unicode 碼以及 UTF-八、UTF-1六、UTF-32編碼。
中文二進制存儲
介紹了這麼多的編碼知識,真正的文件內容是什麼樣子的呢?下面咱們就經過實驗看看在筆者 Linux 機器上 「中文」這兩個字在不一樣的編碼下保存的文件內容。下面是個人實驗過程,有興趣的同窗能夠在本身的機器上重作一下。Window 平臺上的狀況相似這裏就不贅述了。
實驗須要須要使用 2 個工具:
漢字 | Unicode(ucs-2)10進製表示 | Utf-8 | Utf-16 | Utf32 | 區位碼 | GB2312/GBK/GB18030 |
中 | 20013 | E4 B8 AD | 4E2D | 00004E2D | 5448 | D6D0 |
文 | 25991 | E6 96 87 | 6587 | 00006587 | 4636 | CEC4 |
機器環境:
OS: Red Hat Enterprise Linux AS release 4
CPU: Intel (R) Xeon (R) CPU
Locale:LC_ALL=zh_CN.utf-8
//生成 utf8 編碼下的文件 echo –n "中文" > foo.utf8 //檢查 foo 的內容: od -t x1 foo.utf8 0000000 e4 b8 ad e6 96 87 //轉換爲 utf16 編碼 iconv -f utf-8 -t utf-16 foo.utf8 > foo.utf16 //查看 foo.utf16 內容 od -t x1 foo.utf16 0000000 ff fe 2d 4e 87 65 Ff fe 是 BOM(還記得嗎?經過 BOM 來字節流的字節序),其他部分的確是 UTF-16LE 編碼的內容 //轉換爲 utf32 編碼 iconv -f utf-16 -t utf-32 foo.utf16 > foo.utf32 //查看 foo.utf32 內容 od -t x1 foo.utf32 0000000 ff fe 00 00 2d 4e 00 00 87 65 00 00 Ff fe 是 BOM,的確是 UTF-32LE 編碼的內容 //轉換爲 gb2312 編碼 iconv -f utf-8 -t gb2312 foo.txt > foo.gb2312 od -t x1 foo.gb2312 0000000 d6 d0 ce c4 //轉換爲 GBK 編碼 iconv -f utf-8 -t gbk foo.txt > foo.gbk od -t x1 foo.gbk 0000000 d6 d0 ce c4 //轉換爲 GB18030 編碼 iconv -f utf-8 -t gb18030 foo.txt > foo.gb18030 od -t x1 foo.gb18030 0000000 d6 d0 ce c4
C語言中文處理
先明確一個概念:程序內部編碼和程序外部編碼。程序內部編碼指的是中文字符在程序運行時在內存中的編碼形式。程序外部編碼則是中文字符在存儲或者傳輸時的編碼形式。程序外部編碼的最直觀的例子就是當把中文存儲到硬盤文件中時選擇的編碼。
根據程序內部編碼和程序外部編碼是否一致,C/C++的中文處理有兩種常見的方式:
方法 1 的優勢不言而喻,因爲內外統一,不須要進行轉換。不足是若是不是C標準庫支持的編碼方式,那麼字符串處理函數須要本身實現。好比說標準 strlen 函數不能計算中文編碼&UTF-8等的字符串長度,而須要根據編碼標準自行實現。GBK 等中文編碼除了計算字符串長度的函數外,字符串匹配函數也要本身實現(緣由看上文中文編碼總結)。當須要支持的編碼格式不斷增多時,處理函數的開發和維護就須要付出更大的代價。
方法 2 針對方法 1 的不足加以改進。在程序內部能夠優先選擇C標準庫支持的編碼方式,或者根據須要本身實現對某一特定編碼格式的完整支持,這樣任何編碼均可以先轉換爲支持的編碼,代碼通用性比較好。
那麼C標準庫對中文編碼的支持如何呢?目前 Linux 平臺通常使用 GNU C library,內建了對單字節的 char 和寬字符 wchar_t的支持。Char 你們都很熟悉了,處理中文須要的 wchar_t要重點介紹一下。從實現上來講在 Linux 平臺上能夠認爲 wchar_t是 4byte 的 int,內部存儲字符的 UTF32 編碼。因爲標準庫已經內建了對 wchar_t比較完備的支持,如使用 wcslen 計算字符串長度,使用 wcscmp 進行字符串比較等等。因此比較簡單的方式是使用上面的方法2,同時選擇 wchar_t做爲內部字符的表示。作到這一點仍是比較容易的,在輸入輸出的時候經過 mbrtowc/wcrtomb 進行單個字符的內外編碼轉換,以及經過 mbsrtowcs/wcsrtombs 進行字符串的內外編碼轉換便可。這裏須要注意兩點:
關於 locale 的話題比較大,這裏就不深刻了,留待下一篇文章吧.
上面的方法很完美,是嗎?不是嗎?獲得這麼多的好處不是無代價的,最明顯的代價就是內存,任何一個字符,無論中文仍是英文若是保持在 wchar_t裏就須要 4 個 byte,就這一個理由就足以限制了這個方案在關注內存使用的應用場景下的使用。
Python 的中文處理
對 Python 來講因爲內建 Unicode 的支持,因此採用輸入輸出的時候進行轉換,內部保持 Unicode 的方式使用是個不錯的方案。http://docs.python.org/tutorial/introduction.html#unicode-strings 這裏做爲起點,有興趣的同窗自學吧。
編碼選擇建議:
參考資料:
http://baike.baidu.com/view/25492.htm
http://baike.baidu.com/view/25421.htm
http://baike.baidu.com/view/40801.htm
http://www.ibm.com/developerworks/cn/linux/i18n/unicode/linuni/
http://www.gnu.org/software/libc/manual/html_node/index.html
http://www.gnu.org/software/libiconv/
reference:
http://www.fmddlmyy.cn/text7.html
http://www.cnblogs.com/MichaelOwen/articles/2128771.html