1. 基礎
1.1 ASCII碼
我們知道, 在計算機內部, 所有的信息最終都表示為一個二進制的字元串. 每一個二進制
位(bit)有0和1兩種狀態, 因此八個二進制位就可以組合出 256種狀態, 這被稱為一個字
節(byte). 也就是說, 一個字節一共可以用來表示256種不同的狀態, 每一個狀態對應一
個符號, 就是256個符號, 從 0000000到11111111.
上個世紀60年代, 美國制定了一套字符編碼, 對英語字符與二進制位之間的關系, 做了統
一規定. 這被稱為ASCII碼, 一直沿用至今.
ASCII碼一共規定了128個字符的編碼, 比如空格"SPACE"是32(二進制00100000), 大寫的
字母A是65(二進制01000001). 這128個符號(包括32個不能打印出來的控制符號), 只占用
了一個字節的後面7位, 最前面的1位統一規定為0.
1.2 非ASCII編碼
英語用128個符號編碼就夠了, 但是用來表示其他語言, 128個符號是不夠的. 比如, 在法
語中, 字母上方有注音符號, 它就無法用ASCII碼表示. 於是, 一些歐洲國家就决定, 利
用字節中閑置的最高位編入新的符號. 比如, 法語中的é的編碼为130(二進制10000010).
這样一來, 這些歐洲國家使用的編碼體系, 可以表示最多256個符號.
但是, 這裏又出現了新的問題. 不同的國家有不同的字母, 因此, 哪怕它們都使用256個
符號的編碼方式, 代表的字母卻不一样. 比如, 130在法語編碼中代表了é, 在希伯來語
編碼中卻代表了字母Gimel (ג), 在俄語編碼中又會代表另一個符號.
NOTE:
但是不管怎样, 所有這些編碼方式中, 0-127表示的符號是一样的, 不一样的只是128-255
的這一段. // MMMMM
至於亞洲國家的文字, 使用的符號就更多了, 漢字就多達10萬左右. 一個字節只能表示
256種符號, 肯定是不夠的, 就必須使用多個字節表達一個符號. 比如, 簡體中文常見的
編碼方式是GB2312, 使用兩個字節表示一個漢字, 所以理論上最多可以表示
256x256=65536個符號.
2. Unicode
2.1 Unicode的定義
正如上一節所說, 世界上存在着多種編碼方式, 同一個二進制數字可以被解釋成不同的符
號. 因此, 要想打開一個文本文件, 就必須知道它的編碼方式, 否則用錯誤的編碼方式解
讀, 就會出現亂碼. 为什麼電子郵件常常出現亂碼?就是因为發信人和收信人使用的編碼
方式不一样.
可以想象, 如果有一種編碼, 將世界上所有的符號都納入其中. 每一個符號都给予一個獨
一無二的編碼, 那麼亂碼問題就會消失. 這就是Unicode, 就像它的名字都表示的, 這是
一種所有符號的編碼.
Unicode也是一種字符編碼方法, 不過它是由國際組織設計, 可以容納全世界所有語言文
字的編碼方案. Unicode的學名是"Universal Multiple-Octet Coded Character Set",
簡稱为UCS. UCS可以看作是"Unicode Character Set"的縮寫.
Unicode當然是一個很大的集合, 現在的規模可以容納100多萬個符號. 每個符號的編碼都
不一样, 比如, U+0639表示阿拉伯字母Ain, U+0041表示英語的大寫字母A, U+4E25表示漢
字"嚴". 具體的符號對應表, 可以查詢unicode.org, 或者專門的漢字對應表.
2.2 Unicode的問題
需要注意的是, "Unicode只是一個符號集, 它只規定了符號的二進制代碼, 卻沒有規定這
個二進制代碼應該如何存儲".
比如, 漢字"嚴"的unicode是十六進制數4E25, 轉換成二進制數足足有15位
(100111000100101), 也就是說這個符號的表示至少需要2個字節. 表示其他更大的符號,
可能需要3個字節或者4個字節, 甚至更多.
這裏就有兩個嚴重的問題, 第一個問題是, 如何才能區別unicode和ascii?計算機怎麼知
道三個字節表示一個符號, 而不是分別表示三個符號呢?第二個問題是, 我們已經知道,
英文字母只用一個字節表示就夠了, 如果unicode統一規定, 每個符號用三個或四個字節
表示, 那麼每個英文字母前都必然有二到三個字節是0, 這對於存儲來說是極大的浪費,
文本文件的大小會因此大出二三倍, 這是無法接受的.
它們造成的結果是:
1) 出現了unicode的多種存儲方式, 也就是說有許多種不同的二進制格式,
可以用來表示unicode.
2) unicode在很長一段時間內無法推廣, 直到互聯網的出現
3. UTF-8
互聯網的普及, 強烈要求出現一種統一的編碼方式. UTF-8就是在互聯網上使用最廣的一
種unicode的實現方式. 其他實現方式還包括UTF-16和UTF-32, 不過在互聯網上基本不用.
重复一遍, 這裏的關系是, UTF-8是Unicode的實現方式之一.
UTF-8最大的一個特點, 就是它是一種變長的編碼方式. 它可以使用1~6個字節表示一個符
號, 根據不同的符號而變化字節長度.
3.1 UTF-8的編碼規則
UTF-8的編碼規則很簡單, 只有兩條:
1) 對於單字節的符號, 字節的第一位設为0, 後面7位为這個符號的unicode碼. 因此對於
英語字母, UTF-8編碼和ASCII碼是相同的.
2) 對於n字節的符號(n>1), 第一個字節的前n位都設为1, 第n+1位設为0, 後面字節的前
兩位一律設为10. 剩下的沒有提及的二進制位, 全部为這個符號的unicode碼.
下表總結了編碼規則, 字母x表示可用編碼的位.
// #txt---
| Unicode符號範圍 | UTF-8編碼方式
n | (十六進制) | (二進制)
---+-----------------------+------------------------------------------------------
1 | 0000 0000 - 0000 007F | 0xxxxxxx
2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx
3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
表 1. UTF-8的編碼規則
// #txt---end
下面, 還是以漢字"嚴"为例, 演示如何實現UTF-8編碼.
已知"嚴"的unicode是4E25(1001110 00100101), 根據上表, 可以發現4E25處在第三行的
範圍內(0000 0800 - 0000 FFFF), 因此"嚴"的UTF-8編碼需要三個字節, 即格式是
"1110xxxx 10xxxxxx 10xxxxxx". 然後, 從"嚴"的最後一個二進制位開始, 依次從後向前
填入格式中的x, 多出的位補0. 這样就得到了, "嚴"的UTF-8編碼是 "11100100 10111000
10100101", 轉換成十六進制就是E4B8A5.
4. Little endian和Big endian
上一節已經提到, Unicode碼可以采用UCS-2格式直接存儲. 以漢字"嚴"为例, Unicode碼
是4E25, 需要用兩個字節存儲, 一個字節是4E, 另一個字節是25. 存儲的時候, 4E在前,
25在後, 就是Big endian方式; 25在前, 4E在後, 就是Little endian方式.
// Big Endian(4E25) Little Endian(254E)
因此, 第一個字節在前, 就是"大頭方式"(Big endian), 第二個字節在前就是"小頭方式
"(Little endian).
4.1 計算機怎麼知道某一個文件到底采用哪一種方式編碼?(零寬度非換行空格(FEFF))
Unicode規範中定義, 每一個文件的最前面分別加入一個表示編碼順序的字符, 這個字符
的名字叫做"零寬度非換行空格"(ZERO WIDTH NO-BREAK SPACE), 用FEFF表示. 這正好是
兩個字節, 而且FF比FE大1.
// Big Endian(FEFF) Little Endian(FFFE)
NOTE:
如果一個文本文件的頭兩個字節是FE FF, 就表示該文件采用大頭方式; 如果頭兩個字節
是FF FE, 就表示該文件采用小頭方式.
5. Unicode與UTF-8之間的轉換
從表1我們很明顯可以得知Unicode與UTF-8的關系, 下面以C語言實現兩者之間的轉換.
1) 將一個字符的Unicode(UCS-2和UCS-4)編碼轉換成UTF-8編碼.
// #c--- /***************************************************************************** * 將一個字符的Unicode(UCS-2和UCS-4)編碼轉換成UTF-8編碼. * * 参數: * unic 字符的Unicode編碼值 * pOutput 指向輸出的用於存儲UTF8編碼值的緩沖區的指針 * outsize pOutput緩沖的大小 * * 返回值: * 返回轉換後的字符的UTF8編碼所占的字節數, 如果出錯則返回 0 . * * 注意: * 1. UTF8沒有字節序問題, 但是Unicode有字節序要求; * 字節序分为大端(Big Endian)和小端(Little Endian)兩種; * 在Intel處理器中采用小端法表示, 在此采用小端法表示. (低地址存低位) * 2. 請保證 pOutput 緩沖區有最少有 6 字節的空間大小! ****************************************************************************/ int enc_unicode_to_utf8_one(unsigned long unic, unsigned char *pOutput, int outSize) { assert(pOutput != NULL); assert(outSize >= 6); if ( unic <= 0x0000007F ) { // * U-00000000 - U-0000007F: 0xxxxxxx *pOutput = (unic & 0x7F); return 1; } else if ( unic >= 0x00000080 && unic <= 0x000007FF ) { // * U-00000080 - U-000007FF: 110xxxxx 10xxxxxx *(pOutput+1) = (unic & 0x3F) | 0x80; *pOutput = ((unic >> 6) & 0x1F) | 0xC0; return 2; } else if ( unic >= 0x00000800 && unic <= 0x0000FFFF ) { // * U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx *(pOutput+2) = (unic & 0x3F) | 0x80; *(pOutput+1) = ((unic >> 6) & 0x3F) | 0x80; *pOutput = ((unic >> 12) & 0x0F) | 0xE0; return 3; } else if ( unic >= 0x00010000 && unic <= 0x001FFFFF ) { // * U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx *(pOutput+3) = (unic & 0x3F) | 0x80; *(pOutput+2) = ((unic >> 6) & 0x3F) | 0x80; *(pOutput+1) = ((unic >> 12) & 0x3F) | 0x80; *pOutput = ((unic >> 18) & 0x07) | 0xF0; return 4; } else if ( unic >= 0x00200000 && unic <= 0x03FFFFFF ) { // * U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx *(pOutput+4) = (unic & 0x3F) | 0x80; *(pOutput+3) = ((unic >> 6) & 0x3F) | 0x80; *(pOutput+2) = ((unic >> 12) & 0x3F) | 0x80; *(pOutput+1) = ((unic >> 18) & 0x3F) | 0x80; *pOutput = ((unic >> 24) & 0x03) | 0xF8; return 5; } else if ( unic >= 0x04000000 && unic <= 0x7FFFFFFF ) { // * U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx *(pOutput+5) = (unic & 0x3F) | 0x80; *(pOutput+4) = ((unic >> 6) & 0x3F) | 0x80; *(pOutput+3) = ((unic >> 12) & 0x3F) | 0x80; *(pOutput+2) = ((unic >> 18) & 0x3F) | 0x80; *(pOutput+1) = ((unic >> 24) & 0x3F) | 0x80; *pOutput = ((unic >> 30) & 0x01) | 0xFC; return 6; } return 0; } // #c---end
2) 將一個字符的UTF8編碼轉換成Unicode(UCS-2和UCS-4)編碼.
// #c--- /***************************************************************************** * 將一個字符的UTF8編碼轉換成Unicode(UCS-2和UCS-4)編碼. * * 参數: * pInput 指向輸入緩沖區, 以UTF-8編碼 * Unic 指向輸出緩沖區, 其保存的數據即是Unicode編碼值, * 類型为unsigned long . * * 返回值: * 成功則返回該字符的UTF8編碼所占用的字節數; 失敗則返回0. * * 注意: * 1. UTF8沒有字節序問題, 但是Unicode有字節序要求; * 字節序分为大端(Big Endian)和小端(Little Endian)兩種; * 在Intel處理器中采用小端法表示, 在此采用小端法表示. (低地址存低位) ****************************************************************************/ int enc_utf8_to_unicode_one(const unsigned char* pInput, unsigned long *Unic) { assert(pInput != NULL && Unic != NULL); // b1 表示UTF-8編碼的pInput中的高字節, b2 表示次高字節, ... char b1, b2, b3, b4, b5, b6; *Unic = 0x0; // 把 *Unic 初始化为全零 int utfbytes = enc_get_utf8_size(*pInput); unsigned char *pOutput = (unsigned char *) Unic; switch ( utfbytes ) { case 0: *pOutput = *pInput; utfbytes += 1; break; case 2: b1 = *pInput; b2 = *(pInput + 1); if ( (b2 & 0xE0) != 0x80 ) return 0; *pOutput = (b1 << 6) + (b2 & 0x3F); *(pOutput+1) = (b1 >> 2) & 0x07; break; case 3: b1 = *pInput; b2 = *(pInput + 1); b3 = *(pInput + 2); if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) ) return 0; *pOutput = (b2 << 6) + (b3 & 0x3F); *(pOutput+1) = (b1 << 4) + ((b2 >> 2) & 0x0F); break; case 4: b1 = *pInput; b2 = *(pInput + 1); b3 = *(pInput + 2); b4 = *(pInput + 3); if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) || ((b4 & 0xC0) != 0x80) ) return 0; *pOutput = (b3 << 6) + (b4 & 0x3F); *(pOutput+1) = (b2 << 4) + ((b3 >> 2) & 0x0F); *(pOutput+2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03); break; case 5: b1 = *pInput; b2 = *(pInput + 1); b3 = *(pInput + 2); b4 = *(pInput + 3); b5 = *(pInput + 4); if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) ) return 0; *pOutput = (b4 << 6) + (b5 & 0x3F); *(pOutput+1) = (b3 << 4) + ((b4 >> 2) & 0x0F); *(pOutput+2) = (b2 << 2) + ((b3 >> 4) & 0x03); *(pOutput+3) = (b1 << 6); break; case 6: b1 = *pInput; b2 = *(pInput + 1); b3 = *(pInput + 2); b4 = *(pInput + 3); b5 = *(pInput + 4); b6 = *(pInput + 5); if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) || ((b6 & 0xC0) != 0x80) ) return 0; *pOutput = (b5 << 6) + (b6 & 0x3F); *(pOutput+1) = (b5 << 4) + ((b6 >> 2) & 0x0F); *(pOutput+2) = (b3 << 2) + ((b4 >> 4) & 0x03); *(pOutput+3) = ((b1 << 6) & 0x40) + (b2 & 0x3F); break; default: return 0; break; } return utfbytes; } // #c---end
article reproduced:http://rritw.com/a/ITxinwen/hulianwang/20120525/166085.html
article reproduced:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
article reproduced:http://www.joelonsoftware.com/articles/Unicode.html
article reproduced:http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631.html
article reproduced:http://www.ietf.org/rfc/rfc3629.txt
留言列表