UTF8編碼轉換為GB2312編碼字符集時,需要明確以下兩點:
-
UTF8是編碼格式,而GB2312是字符集,UTF8可以動態的表示1到6字節的編碼范圍,其還原后可以是雙字節Unicode UTF16(USC2)字符集,也可以是四字節Unicode UTF32(USC4)字符集,四字節以上的很少用到可以不做兼容處理。
-
這兩種編碼是完全不兼容的,因此他們之間的轉換無法通過既有規則形成公式之間計算,僅能通過LUT(look up table)查找表模式實現;這也就意味着必須先准備一張包含UTF8編碼下的字符對應GB2312字符集下字符的表。
GB2312字符集概述:
GB2312字符集通常指GB2312-80格式,使用2字節(16bit)表示一個字符,分區碼和位碼兩部分,區碼在高字節,位碼在低字節,在解析時需要注意。GB2312-80共編碼7445個字符,其中漢字有6763個,全字符集編碼范圍A1A1-F7FE;其中中文字符編碼范圍B0A1-F7FE,可見該編碼能與單字節的ASCII編碼0x00~0x7F做出很好的區分。
官方資料請移步“GB2312 80信息交換用漢字編碼字符集 基本集”http://www.moe.gov.cn/s78/A19/yxs_left/moe_810/s230/201206/t20120601_136847.html
但該文檔僅2頁,幾乎沒有參考價值:(
UTF8編碼方式概述:
Unicode碼 (hex) | UTF8碼 (hex) |
---|---|
0000 0000 0000 007F | 0xxxxxxx |
0000 0080 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
單字節情況:UTF8編碼中單字節最高位為0
,UTF8兼容ASCII編碼0x00~0x7F
。
多字節情況:第一個字節從高位開始連續的1
的個數(這里1
的個數計算方法為從最高位開始計數,直至遇到當前字節
的10
結束)表示后續還有多少位是連續位;后續數據字節高兩位固定 10
,由於第一字節自身可以攜帶部分數據;所以也受數據字節高兩位固定的規則限制。
舉個栗子:
雙字節的Unicode編碼為UTF8格式,110xxxxx 10xxxxxx
,閱讀方向從左往右依次為第一字節、第二字節,字節計數基准點為0
;第一字節最高位為1
表示該字節后還有一個字節的數據;第6,5位為10
表示本字節數據段開始,第4,0位為xxxxx
表示可以攜帶5比特的數據;第二字節就非常簡單了,高位10
打頭,直接取剩下的6比特數據即可。
因此2字節的UTF8編碼僅有11比特的有效數據區,比起直接使用UTF16(USC2)編碼還少了5比特信息范圍,但是UTF8的優勢在於可變長的設計,從解碼規則也可以看出,對於西方國家的ASCII碼,僅需一個字節就能表示,在網絡傳輸方面能省點帶寬;但對於東亞文字,就不那么友好了,兩個字節能表示的數據范圍,編碼到UTF8格式需要3個字節才能表示,可以說有利有弊。
UTF8編碼轉換為GB2312編碼字符集轉換思路:
1.解碼UTF8,還原成Unicode字符集
2.使用Unicode到GB2312字符集映射表找到對應的GB2312字符集編碼
3.對於點陣字體,使用GB2312字符集編碼在字庫中查找字模進行顯示;對於TTF字體,使用GB2312字符集編碼找到字體的繪制path,進行繪制渲染。
值得慶幸的是GB2312字符集所表示的漢字/符號剛好在雙字節的Unicode16(USC2)編碼范圍內,一對Unicode16轉GB2312正好是4個字節,非常方便32位的系統進行對齊尋址,尤其是ARM這種不支持非對其訪問的平台。
代碼實現:
/*
Unicode符號范圍 | UTF-8編碼方式
(十六進制) | (二進制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
/**
* @brief utf8編碼轉unicode字符集(usc4),最大支持4字節utf8編碼,(4字節以上在中日韓文中為生僻字)
* @param *utf8 utf8變長編碼字節集1~4個字節
* @param *unicode utf8編碼轉unicode字符集結果,最大4個字節,返回的字節序與utf8編碼序一致
* @return length 0:utf8解碼異常,others:本次utf8編碼長度
*/
uint8_t UTF8ToUnicode(uint8_t *utf8, uint32_t *unicode) {
const uint8_t lut_size = 3;
const uint8_t length_lut[] = {2, 3, 4};
const uint8_t range_lut[] = {0xE0, 0xF0, 0xF8};
const uint8_t mask_lut[] = {0x1F, 0x0F, 0x07};
uint8_t length = 0;
byte b = *(utf8 + 0);
uint32_t i = 0;
if(utf8 == NULL) {
*unicode = 0;
return PARSE_ERROR;
}
// utf8編碼兼容ASCII編碼,使用0xxxxxx 表示00~7F
if(b < 0x80) {
*unicode = b;
return 1;
}
// utf8不兼容ISO8859-1 ASCII拓展字符集
// 同時最大支持編碼6個字節即1111110X
if(b < 0xC0 || b > 0xFD) {
*unicode = 0;
return PARSE_ERROR;
}
for(i = 0; i < lut_size; i++) {
if(b < range_lut[i]) {
*unicode = b & mask_lut[i];
length = length_lut[i];
break;
}
}
// 超過四字節的utf8編碼不進行解析
if(length == 0) {
*unicode = 0;
return PARSE_ERROR;
}
// 取后續字節數據
for(i = 1; i < length; i++ ) {
b = *(utf8 + i);
// 多字節utf8編碼后續字節范圍10xxxxxx~10111111
if( b < 0x80 || b > 0xBF ) {
break;
}
*unicode <<= 6;
// 00111111
*unicode |= (b & 0x3F);
}
// 長度校驗
return (i < length) ? PARSE_ERROR : length;
}
/**
* @brief 4字節unicode(usc4)字符集轉utf16編碼
* @param unicode unicode字符值
* @param *utf16 utf16編碼結果
* @return utf16長度,(2字節)單位
*/
uint8_t UnicodeToUTF16(uint32_t unicode, uint16_t *utf16) {
// Unicode范圍 U+000~U+FFFF
// utf16編碼方式:2 Byte存儲,編碼后等於Unicode值
if(unicode <= 0xFFFF) {
if(utf16 != NULL) {
*utf16 = (unicode & 0xFFFF);
}
return 1;
}else if( unicode <= 0xEFFFF ) {
if( utf16 != NULL ) {
// 高10位
*(utf16 + 0) = 0xD800 + (unicode >> 10) - 0x40;
// 低10位
*(utf16 + 1) = 0xDC00 + (unicode &0x03FF);
}
return 2;
}
return 0;
}
測試樣例
int main() {
// 嚴 utf8 E4 B8 A5
printf("Hello world!\n");
uint32_t buffer;
uint8_t utf8[] = "\xE4\xB8\xA5";
uint16_t utf16[2] = {0};
uint8_t len = UTF8ToUnicode(utf8, &buffer);
printf("len:%d\n", len);
printf("buffer:%x\n", buffer);
len = UnicodeToUTF16(buffer, utf16);
printf("len:%d\n", len);
printf("utf16[0]:%x\n", utf16[0]);
printf("utf16[0]:%x\n", utf16[1]);
return 0;
}
輸出:
驗證:
tips:
Unicode 16與GB2312-80的查找表可以使Unicode 16字符集編碼按照升序排列,(即Unicode 16從小到大排,GB2312與之對應,但GB2312是亂的)這樣程序中使用二分法查表,時間復雜度是O(log(n)),比線性查找最壞情況的O(n)要快上許多。
附件:
utf16.bin
utf16->gb2312查找表,其中utf16字符集升序排序,二進制格式
gb2312.bin
gb2312->utf16查找表,其中gb2312字符集升序排序,二進制格式
RawFile.txt
文本格式的UTF16到GB2312對照表,其中gb2312字符集升序排序