當(dāng)前位置:
首頁 >
Unicode与UTF-8互转(C语言实现)
發(fā)布時間:2025/3/20
63
豆豆
生活随笔
收集整理的這篇文章主要介紹了
Unicode与UTF-8互转(C语言实现)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1. 基礎(chǔ)
1.1 ASCII碼
我們知道, 在計算機內(nèi)部, 所有的信息最終都表示為一個二進(jìn)制的字符串. 每一個二進(jìn)制
位(bit)有0和1兩種狀態(tài), 因此八個二進(jìn)制位就可以組合出 256種狀態(tài), 這被稱為一個字
節(jié)(byte). 也就是說, 一個字節(jié)一共可以用來表示256種不同的狀態(tài), 每一個狀態(tài)對應(yīng)一
個符號, 就是256個符號, 從 0000000到11111111.
上個世紀(jì)60年代, 美國制定了一套字符編碼, 對英語字符與二進(jìn)制位之間的關(guān)系, 做了統(tǒng)
一規(guī)定. 這被稱為ASCII碼, 一直沿用至今.
ASCII碼一共規(guī)定了128個字符的編碼, 比如空格"SPACE"是32(二進(jìn)制00100000), 大寫的
字母A是65(二進(jìn)制01000001). 這128個符號(包括32個不能打印出來的控制符號), 只占用
了一個字節(jié)的后面7位, 最前面的1位統(tǒng)一規(guī)定為0.
1.2 非ASCII編碼
英語用128個符號編碼就夠了, 但是用來表示其他語言, 128個符號是不夠的. 比如, 在法
語中, 字母上方有注音符號, 它就無法用ASCII碼表示. 于是, 一些歐洲國家就決定, 利
用字節(jié)中閑置的最高位編入新的符號. 比如, 法語中的é的編碼為130(二進(jìn)制10000010).
這樣一來, 這些歐洲國家使用的編碼體系, 可以表示最多256個符號.
但是, 這里又出現(xiàn)了新的問題. 不同的國家有不同的字母, 因此, 哪怕它們都使用256個
符號的編碼方式, 代表的字母卻不一樣. 比如, 130在法語編碼中代表了é, 在希伯來語
編碼中卻代表了字母Gimel?(?), 在俄語編碼中又會代表另一個符號.
NOTE:
但是不管怎樣, 所有這些編碼方式中, 0-127表示的符號是一樣的, 不一樣的只是128-255
的這一段.?// MMMMM
至于亞洲國家的文字, 使用的符號就更多了, 漢字就多達(dá)10萬左右. 一個字節(jié)只能表示
256種符號, 肯定是不夠的, 就必須使用多個字節(jié)表達(dá)一個符號. 比如, 簡體中文常見的
編碼方式是GB2312, 使用兩個字節(jié)表示一個漢字, 所以理論上最多可以表示
256x256=65536個符號.
2. Unicode
2.1 Unicode的定義
正如上一節(jié)所說, 世界上存在著多種編碼方式, 同一個二進(jìn)制數(shù)字可以被解釋成不同的符
號. 因此, 要想打開一個文本文件, 就必須知道它的編碼方式, 否則用錯誤的編碼方式解
讀, 就會出現(xiàn)亂碼. 為什么電子郵件常常出現(xiàn)亂碼?就是因為發(fā)信人和收信人使用的編碼
方式不一樣.
可以想象, 如果有一種編碼, 將世界上所有的符號都納入其中. 每一個符號都給予一個獨
一無二的編碼, 那么亂碼問題就會消失. 這就是Unicode, 就像它的名字都表示的, 這是
一種所有符號的編碼.
Unicode也是一種字符編碼方法, 不過它是由國際組織設(shè)計, 可以容納全世界所有語言文
字的編碼方案. Unicode的學(xué)名是"Universal Multiple-Octet Coded Character Set",
簡稱為UCS. UCS可以看作是"Unicode Character Set"的縮寫.
Unicode當(dāng)然是一個很大的集合, 現(xiàn)在的規(guī)模可以容納100多萬個符號. 每個符號的編碼都
不一樣, 比如, U+0639表示阿拉伯字母Ain, U+0041表示英語的大寫字母A, U+4E25表示漢
字"嚴(yán)". 具體的符號對應(yīng)表, 可以查詢unicode.org, 或者專門的漢字對應(yīng)表.
2.2 Unicode的問題
需要注意的是, "Unicode只是一個符號集, 它只規(guī)定了符號的二進(jìn)制代碼, 卻沒有規(guī)定這
個二進(jìn)制代碼應(yīng)該如何存儲".
比如, 漢字"嚴(yán)"的unicode是十六進(jìn)制數(shù)4E25, 轉(zhuǎn)換成二進(jìn)制數(shù)足足有15位
(100111000100101), 也就是說這個符號的表示至少需要2個字節(jié). 表示其他更大的符號,
可能需要3個字節(jié)或者4個字節(jié), 甚至更多.
這里就有兩個嚴(yán)重的問題, 第一個問題是, 如何才能區(qū)別unicode和ascii?計算機怎么知
道三個字節(jié)表示一個符號, 而不是分別表示三個符號呢?第二個問題是, 我們已經(jīng)知道,
英文字母只用一個字節(jié)表示就夠了, 如果unicode統(tǒng)一規(guī)定, 每個符號用三個或四個字節(jié)
表示, 那么每個英文字母前都必然有二到三個字節(jié)是0, 這對于存儲來說是極大的浪費,
文本文件的大小會因此大出二三倍, 這是無法接受的.
它們造成的結(jié)果是:
1)??出現(xiàn)了unicode的多種存儲方式, 也就是說有許多種不同的二進(jìn)制格式,
????可以用來表示unicode.
2)??unicode在很長一段時間內(nèi)無法推廣, 直到互聯(lián)網(wǎng)的出現(xiàn)
3. UTF-8
互聯(lián)網(wǎng)的普及, 強烈要求出現(xiàn)一種統(tǒng)一的編碼方式. UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一
種unicode的實現(xiàn)方式. 其他實現(xiàn)方式還包括UTF-16和UTF-32, 不過在互聯(lián)網(wǎng)上基本不用.
重復(fù)一遍, 這里的關(guān)系是, UTF-8是Unicode的實現(xiàn)方式之一.
UTF-8最大的一個特點, 就是它是一種變長的編碼方式. 它可以使用1~6個字節(jié)表示一個符
號, 根據(jù)不同的符號而變化字節(jié)長度.
3.1 UTF-8的編碼規(guī)則
UTF-8的編碼規(guī)則很簡單, 只有兩條:
1)?對于單字節(jié)的符號, 字節(jié)的第一位設(shè)為0, 后面7位為這個符號的unicode碼. 因此對于
?? 英語字母, UTF-8編碼和ASCII碼是相同的.
2)?對于n字節(jié)的符號(n>1), 第一個字節(jié)的前n位都設(shè)為1, 第n+1位設(shè)為0, 后面字節(jié)的前
?? 兩位一律設(shè)為10. 剩下的沒有提及的二進(jìn)制位, 全部為這個符號的unicode碼.
下表總結(jié)了編碼規(guī)則, 字母x表示可用編碼的位.
// #txt---| Unicode符號范圍 | UTF-8編碼方式n | (十六進(jìn)制) | (二進(jìn)制) ---+-----------------------+------------------------------------------------------1 | 0000 0000 - 0000 007F | 0xxxxxxx2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx表 1. UTF-8的編碼規(guī)則 // #txt---end
下面, 還是以漢字" 嚴(yán) "為例, 演示如何實現(xiàn)UTF-8編碼.
已知" 嚴(yán) "的unicode是4E25 (1001110 00100101) , 根據(jù)上表, 可以發(fā)現(xiàn)4E25處在第三行的
范圍內(nèi) (0000 0800 - 0000 FFFF) , 因此" 嚴(yán) "的UTF-8編碼需要三個字節(jié), 即格式是
" 1110xxxx 10xxxxxx 10xxxxxx ". 然后, 從" 嚴(yán) "的最后一個二進(jìn)制位開始, 依次從后向前
填入格式中的x, 多出的位補0. 這樣就得到了, " 嚴(yán) "的UTF-8編碼是 " 11100100 10111000
10100101", 轉(zhuǎn)換成十六進(jìn)制就是E4B8A5.
4. Little endian和Big endian
上一節(jié)已經(jīng)提到, Unicode碼可以采用UCS-2格式直接存儲. 以漢字" 嚴(yán) "為例, Unicode碼
是4E25, 需要用兩個字節(jié)存儲, 一個字節(jié)是4E, 另一個字節(jié)是25. 存儲的時候, 4E在前,
25在后, 就是Big endian方式; 25在前, 4E在后, 就是Little endian方式.
// Big Endian(4E25)????Little Endian(254E)
因此, 第一個字節(jié)在前, 就是" 大頭方式 " (Big endian) , 第二個字節(jié)在前就是" 小頭方式
" (Little endian) .
4.1 計算機怎么知道某一個文件到底采用哪一種方式編碼?(零寬度非換行空格(FEFF))
Unicode規(guī)范中定義, 每一個文件的最前面分別加入一個表示編碼順序的字符, 這個字符
的名字叫做" 零寬度非換行空格 " (ZERO WIDTH NO-BREAK SPACE) , 用FEFF表示. 這正好是
兩個字節(jié), 而且FF比FE大1.
// Big Endian(FEFF)????Little Endian(FFFE)
NOTE :
如果一個文本文件的頭兩個字節(jié)是FE FF, 就表示該文件采用大頭方式; 如果頭兩個字節(jié)
是FF FE, 就表示該文件采用小頭方式.
5. Unicode與UTF-8之間的轉(zhuǎn)換
從表1我們很明顯可以得知Unicode與UTF-8的關(guān)系, 下面以C語言實現(xiàn)兩者之間的轉(zhuǎn)換.
1) 將一個字符的Unicode(UCS-2和UCS-4)編碼轉(zhuǎn)換成UTF-8編碼.// #c--- /****************************************************************************** 將一個字符的Unicode(UCS-2和UCS-4)編碼轉(zhuǎn)換成UTF-8編碼.** 參數(shù):* unic 字符的Unicode編碼值* pOutput 指向輸出的用于存儲UTF8編碼值的緩沖區(qū)的指針* outsize pOutput緩沖的大小** 返回值:* 返回轉(zhuǎn)換后的字符的UTF8編碼所占的字節(jié)數(shù), 如果出錯則返回 0 .** 注意:* 1. UTF8沒有字節(jié)序問題, 但是Unicode有字節(jié)序要求;* 字節(jié)序分為大端(Big Endian)和小端(Little Endian)兩種;* 在Intel處理器中采用小端法表示, 在此采用小端法表示. (低地址存低位)* 2. 請保證 pOutput 緩沖區(qū)有最少有 6 字節(jié)的空間大小!****************************************************************************/ 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編碼轉(zhuǎn)換成Unicode (UCS-2和UCS-4) 編碼.
// #c--- /****************************************************************************** 將一個字符的UTF8編碼轉(zhuǎn)換成Unicode(UCS-2和UCS-4)編碼.** 參數(shù):* pInput 指向輸入緩沖區(qū), 以UTF-8編碼* Unic 指向輸出緩沖區(qū), 其保存的數(shù)據(jù)即是Unicode編碼值,* 類型為unsigned long .** 返回值:* 成功則返回該字符的UTF8編碼所占用的字節(jié)數(shù); 失敗則返回0.** 注意:* 1. UTF8沒有字節(jié)序問題, 但是Unicode有字節(jié)序要求;* 字節(jié)序分為大端(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中的高字節(jié), b2 表示次高字節(jié), ...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
6. 延伸閱讀
*? http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
*? The Absolute Minimum Every Software Developer Absolutely, Positively Must
??Know About Unicode and Character Sets (關(guān)于字符集的最基本知識)
???? http://www.joelonsoftware.com/articles/Unicode.html
*? 談?wù)刄nicode編碼
???? http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631.html
*? RFC3629: UTF-8, a? tr ansfo rm ation format of ISO 10646 (如果實現(xiàn)UTF-8的規(guī)定)
1.1 ASCII碼
我們知道, 在計算機內(nèi)部, 所有的信息最終都表示為一個二進(jìn)制的字符串. 每一個二進(jìn)制
位(bit)有0和1兩種狀態(tài), 因此八個二進(jìn)制位就可以組合出 256種狀態(tài), 這被稱為一個字
節(jié)(byte). 也就是說, 一個字節(jié)一共可以用來表示256種不同的狀態(tài), 每一個狀態(tài)對應(yīng)一
個符號, 就是256個符號, 從 0000000到11111111.
上個世紀(jì)60年代, 美國制定了一套字符編碼, 對英語字符與二進(jìn)制位之間的關(guān)系, 做了統(tǒng)
一規(guī)定. 這被稱為ASCII碼, 一直沿用至今.
ASCII碼一共規(guī)定了128個字符的編碼, 比如空格"SPACE"是32(二進(jìn)制00100000), 大寫的
字母A是65(二進(jìn)制01000001). 這128個符號(包括32個不能打印出來的控制符號), 只占用
了一個字節(jié)的后面7位, 最前面的1位統(tǒng)一規(guī)定為0.
1.2 非ASCII編碼
英語用128個符號編碼就夠了, 但是用來表示其他語言, 128個符號是不夠的. 比如, 在法
語中, 字母上方有注音符號, 它就無法用ASCII碼表示. 于是, 一些歐洲國家就決定, 利
用字節(jié)中閑置的最高位編入新的符號. 比如, 法語中的é的編碼為130(二進(jìn)制10000010).
這樣一來, 這些歐洲國家使用的編碼體系, 可以表示最多256個符號.
但是, 這里又出現(xiàn)了新的問題. 不同的國家有不同的字母, 因此, 哪怕它們都使用256個
符號的編碼方式, 代表的字母卻不一樣. 比如, 130在法語編碼中代表了é, 在希伯來語
編碼中卻代表了字母Gimel?(?), 在俄語編碼中又會代表另一個符號.
NOTE:
但是不管怎樣, 所有這些編碼方式中, 0-127表示的符號是一樣的, 不一樣的只是128-255
的這一段.?// MMMMM
至于亞洲國家的文字, 使用的符號就更多了, 漢字就多達(dá)10萬左右. 一個字節(jié)只能表示
256種符號, 肯定是不夠的, 就必須使用多個字節(jié)表達(dá)一個符號. 比如, 簡體中文常見的
編碼方式是GB2312, 使用兩個字節(jié)表示一個漢字, 所以理論上最多可以表示
256x256=65536個符號.
2. Unicode
2.1 Unicode的定義
正如上一節(jié)所說, 世界上存在著多種編碼方式, 同一個二進(jìn)制數(shù)字可以被解釋成不同的符
號. 因此, 要想打開一個文本文件, 就必須知道它的編碼方式, 否則用錯誤的編碼方式解
讀, 就會出現(xiàn)亂碼. 為什么電子郵件常常出現(xiàn)亂碼?就是因為發(fā)信人和收信人使用的編碼
方式不一樣.
可以想象, 如果有一種編碼, 將世界上所有的符號都納入其中. 每一個符號都給予一個獨
一無二的編碼, 那么亂碼問題就會消失. 這就是Unicode, 就像它的名字都表示的, 這是
一種所有符號的編碼.
Unicode也是一種字符編碼方法, 不過它是由國際組織設(shè)計, 可以容納全世界所有語言文
字的編碼方案. Unicode的學(xué)名是"Universal Multiple-Octet Coded Character Set",
簡稱為UCS. UCS可以看作是"Unicode Character Set"的縮寫.
Unicode當(dāng)然是一個很大的集合, 現(xiàn)在的規(guī)模可以容納100多萬個符號. 每個符號的編碼都
不一樣, 比如, U+0639表示阿拉伯字母Ain, U+0041表示英語的大寫字母A, U+4E25表示漢
字"嚴(yán)". 具體的符號對應(yīng)表, 可以查詢unicode.org, 或者專門的漢字對應(yīng)表.
2.2 Unicode的問題
需要注意的是, "Unicode只是一個符號集, 它只規(guī)定了符號的二進(jìn)制代碼, 卻沒有規(guī)定這
個二進(jìn)制代碼應(yīng)該如何存儲".
比如, 漢字"嚴(yán)"的unicode是十六進(jìn)制數(shù)4E25, 轉(zhuǎn)換成二進(jìn)制數(shù)足足有15位
(100111000100101), 也就是說這個符號的表示至少需要2個字節(jié). 表示其他更大的符號,
可能需要3個字節(jié)或者4個字節(jié), 甚至更多.
這里就有兩個嚴(yán)重的問題, 第一個問題是, 如何才能區(qū)別unicode和ascii?計算機怎么知
道三個字節(jié)表示一個符號, 而不是分別表示三個符號呢?第二個問題是, 我們已經(jīng)知道,
英文字母只用一個字節(jié)表示就夠了, 如果unicode統(tǒng)一規(guī)定, 每個符號用三個或四個字節(jié)
表示, 那么每個英文字母前都必然有二到三個字節(jié)是0, 這對于存儲來說是極大的浪費,
文本文件的大小會因此大出二三倍, 這是無法接受的.
它們造成的結(jié)果是:
1)??出現(xiàn)了unicode的多種存儲方式, 也就是說有許多種不同的二進(jìn)制格式,
????可以用來表示unicode.
2)??unicode在很長一段時間內(nèi)無法推廣, 直到互聯(lián)網(wǎng)的出現(xiàn)
3. UTF-8
互聯(lián)網(wǎng)的普及, 強烈要求出現(xiàn)一種統(tǒng)一的編碼方式. UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一
種unicode的實現(xiàn)方式. 其他實現(xiàn)方式還包括UTF-16和UTF-32, 不過在互聯(lián)網(wǎng)上基本不用.
重復(fù)一遍, 這里的關(guān)系是, UTF-8是Unicode的實現(xiàn)方式之一.
UTF-8最大的一個特點, 就是它是一種變長的編碼方式. 它可以使用1~6個字節(jié)表示一個符
號, 根據(jù)不同的符號而變化字節(jié)長度.
3.1 UTF-8的編碼規(guī)則
UTF-8的編碼規(guī)則很簡單, 只有兩條:
1)?對于單字節(jié)的符號, 字節(jié)的第一位設(shè)為0, 后面7位為這個符號的unicode碼. 因此對于
?? 英語字母, UTF-8編碼和ASCII碼是相同的.
2)?對于n字節(jié)的符號(n>1), 第一個字節(jié)的前n位都設(shè)為1, 第n+1位設(shè)為0, 后面字節(jié)的前
?? 兩位一律設(shè)為10. 剩下的沒有提及的二進(jìn)制位, 全部為這個符號的unicode碼.
下表總結(jié)了編碼規(guī)則, 字母x表示可用編碼的位.
// #txt---| Unicode符號范圍 | UTF-8編碼方式n | (十六進(jìn)制) | (二進(jìn)制) ---+-----------------------+------------------------------------------------------1 | 0000 0000 - 0000 007F | 0xxxxxxx2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx表 1. UTF-8的編碼規(guī)則 // #txt---end
下面, 還是以漢字" 嚴(yán) "為例, 演示如何實現(xiàn)UTF-8編碼.
已知" 嚴(yán) "的unicode是4E25 (1001110 00100101) , 根據(jù)上表, 可以發(fā)現(xiàn)4E25處在第三行的
范圍內(nèi) (0000 0800 - 0000 FFFF) , 因此" 嚴(yán) "的UTF-8編碼需要三個字節(jié), 即格式是
" 1110xxxx 10xxxxxx 10xxxxxx ". 然后, 從" 嚴(yán) "的最后一個二進(jìn)制位開始, 依次從后向前
填入格式中的x, 多出的位補0. 這樣就得到了, " 嚴(yán) "的UTF-8編碼是 " 11100100 10111000
10100101", 轉(zhuǎn)換成十六進(jìn)制就是E4B8A5.
4. Little endian和Big endian
上一節(jié)已經(jīng)提到, Unicode碼可以采用UCS-2格式直接存儲. 以漢字" 嚴(yán) "為例, Unicode碼
是4E25, 需要用兩個字節(jié)存儲, 一個字節(jié)是4E, 另一個字節(jié)是25. 存儲的時候, 4E在前,
25在后, 就是Big endian方式; 25在前, 4E在后, 就是Little endian方式.
// Big Endian(4E25)????Little Endian(254E)
因此, 第一個字節(jié)在前, 就是" 大頭方式 " (Big endian) , 第二個字節(jié)在前就是" 小頭方式
" (Little endian) .
4.1 計算機怎么知道某一個文件到底采用哪一種方式編碼?(零寬度非換行空格(FEFF))
Unicode規(guī)范中定義, 每一個文件的最前面分別加入一個表示編碼順序的字符, 這個字符
的名字叫做" 零寬度非換行空格 " (ZERO WIDTH NO-BREAK SPACE) , 用FEFF表示. 這正好是
兩個字節(jié), 而且FF比FE大1.
// Big Endian(FEFF)????Little Endian(FFFE)
NOTE :
如果一個文本文件的頭兩個字節(jié)是FE FF, 就表示該文件采用大頭方式; 如果頭兩個字節(jié)
是FF FE, 就表示該文件采用小頭方式.
5. Unicode與UTF-8之間的轉(zhuǎn)換
從表1我們很明顯可以得知Unicode與UTF-8的關(guān)系, 下面以C語言實現(xiàn)兩者之間的轉(zhuǎn)換.
1) 將一個字符的Unicode(UCS-2和UCS-4)編碼轉(zhuǎn)換成UTF-8編碼.// #c--- /****************************************************************************** 將一個字符的Unicode(UCS-2和UCS-4)編碼轉(zhuǎn)換成UTF-8編碼.** 參數(shù):* unic 字符的Unicode編碼值* pOutput 指向輸出的用于存儲UTF8編碼值的緩沖區(qū)的指針* outsize pOutput緩沖的大小** 返回值:* 返回轉(zhuǎn)換后的字符的UTF8編碼所占的字節(jié)數(shù), 如果出錯則返回 0 .** 注意:* 1. UTF8沒有字節(jié)序問題, 但是Unicode有字節(jié)序要求;* 字節(jié)序分為大端(Big Endian)和小端(Little Endian)兩種;* 在Intel處理器中采用小端法表示, 在此采用小端法表示. (低地址存低位)* 2. 請保證 pOutput 緩沖區(qū)有最少有 6 字節(jié)的空間大小!****************************************************************************/ 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編碼轉(zhuǎn)換成Unicode (UCS-2和UCS-4) 編碼.
// #c--- /****************************************************************************** 將一個字符的UTF8編碼轉(zhuǎn)換成Unicode(UCS-2和UCS-4)編碼.** 參數(shù):* pInput 指向輸入緩沖區(qū), 以UTF-8編碼* Unic 指向輸出緩沖區(qū), 其保存的數(shù)據(jù)即是Unicode編碼值,* 類型為unsigned long .** 返回值:* 成功則返回該字符的UTF8編碼所占用的字節(jié)數(shù); 失敗則返回0.** 注意:* 1. UTF8沒有字節(jié)序問題, 但是Unicode有字節(jié)序要求;* 字節(jié)序分為大端(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中的高字節(jié), b2 表示次高字節(jié), ...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
6. 延伸閱讀
*? http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
*? The Absolute Minimum Every Software Developer Absolutely, Positively Must
??Know About Unicode and Character Sets (關(guān)于字符集的最基本知識)
???? http://www.joelonsoftware.com/articles/Unicode.html
*? 談?wù)刄nicode編碼
???? http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631.html
*? RFC3629: UTF-8, a? tr ansfo rm ation format of ISO 10646 (如果實現(xiàn)UTF-8的規(guī)定)
????http://www.ietf.org/rfc/rfc3629.txt
總結(jié)
以上是生活随笔為你收集整理的Unicode与UTF-8互转(C语言实现)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++多字节与宽字节字符串转换(wind
- 下一篇: 在服务器上用Fiddler抓取HTTPS