1.UTF-8 轉換 Unicode
在編寫FTP Client時,發現通過recv獲取的數據是採用UTF-8方式進行編碼的,直接用Unicode方式進行顯示時會發生錯誤。採用MultiByteToWideChar也無法正確轉換(default是Ascii
to Unicode。是我的設置問題?沒有仔細研究)。
因此學習了下UTF-8的編碼原理,參考如下:
標準的UTF-8是有一個頭(是EF BB BF)和Unicode有一個(FF FE頭一樣),每一個字可以由一個byte(如:英文字母、數字),也可以由二個byte(如:泛歐語系、斯拉夫語字母),也可以由三個byte組成(如:漢字)。一般由四個byte組成的字很少很少。UTF-8編碼模板如下:
UCS-4 range (hex.) UTF-8 octet sequence (binary)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
從這個頭之後的第一個byte,如果是0xxxxxxx形式,就說明這個字只有一個byte組成,如果是110xxxxx形式就說明這個字有二個byte組成,如果是1110xxxx形式就說明有三個byte組成,如果是11110xxx就說明有四個byte組成。
轉換算法可以簡單的理解爲:
首先判斷這個UTF-8有沒有頭(EF BB BF),沒有從第一個byte開始處理,有頭就跳三個byte,讀到真正的內容。沒有的話,第一個byte就是真正的內容。再把讀到的第一個byte分析成(0000 0000---1111 1111)和UTF-8編碼格式相比較,得出這個真正的字有幾個位。如果是隻有一個byte(格式爲0xxxxxxx),直接就把這個字符以寬字符寫進文件就行了。如果是二byte(格式爲110xxxxx),我們就要算出xxxxx處的值,再讀一個byte得到10xxxxxx也同樣要算出xxxxxx,把這兩個連在一起xxxxx和xxxxx組成一個二進制數,得到這個二進數的十進制值,再以寬字符寫進文件就行了。如果是有三位(格式爲1110xxxx),要再讀兩個byte得到10xxxxxx和10xxxxxx,把這xxxx
和xxxxxx和xxxxxx連在一起組成二進制數,把他再轉成十進制,把這個值以寬字符寫進文件。三個字節、四個字節的一次類推。
簡單的轉換code參考Unicode下char*(UTF-8)轉CString(Unicode):
CString CTTXDlg::g_f_pchar_to_wcs(const unsigned char * p )
{
CString wstr(_T(""));
wchar_t wc=L'1';
size_t r=0; //size_t unsigned integer Result of sizeof operator
while (1)
{
if(*p==NULL||*p==char(0))//原文爲*p=='0',有錯,改*p==char(0)
break;
r=g_f_u8towc(wc,p);//從UTF-8格式的地址中,讀一個wchar_t出來
if(r==-1)//出現錯誤
AfxMessageBox(_T("g_f_u8towc error"));
p=p+r; //移位,準備下一次的轉換
wstr+=wc;//給CString 附值
}
return wstr;
}
size_t CTTXDlg::g_f_u8towc(wchar_t &dest_wchar, const unsigned char * src_str)
{
int count_bytes = 0;
unsigned char byte_one = 0, byte_other = 0x3f; // 用於位與運算以提取位值 0x3f-->00111111
wchar_t tmp_wchar = L'0';
if (!src_str)
return (size_t)-1;
for (;;) // 檢測字節序列長度,根據第一個字節頭的1個個數
{
if (src_str[0] <= 0x7f){
count_bytes = 1; // ASCII字符: 0xxxxxxx( ~ 01111111)
byte_one = 0x7f; // 用於位與運算, 提取有效位值, 下同 01111111
break;
}
if ( (src_str[0] >= 0xc0) && (src_str[0] <= 0xdf) ){
count_bytes = 2; // 110xxxxx(110 00000 ~ 110 111111)
byte_one = 0x1f; //00011111 第一字節有效位的個數
break;
}
if ( (src_str[0] >= 0xe0) && (src_str[0] <= 0xef) ){
count_bytes = 3; // 1110xxxx(1110 0000 ~ 1110 1111)
byte_one = 0xf; //00001111
break;
}
if ( (src_str[0] >= 0xf0) && (src_str[0] <= 0xf7) ){
count_bytes = 4; // 11110xxx(11110 000 ~ 11110 111)
byte_one = 0x7;
break;
}
if ( (src_str[0] >= 0xf8) && (src_str[0] <= 0xfb) ){
count_bytes = 5; // 111110xx(111110 00 ~ 111110 11)
byte_one = 0x3;
break;
}
if ( (src_str[0] >= 0xfc) && (src_str[0] <= 0xfd) ){
count_bytes = 6; // 1111110x(1111110 0 ~ 1111110 1)
byte_one = 0x1;
break;
}
return (size_t)-1; // 以上皆不滿足則爲非法序列
}
// 以下幾行析取UTF-8編碼字符各個字節的有效位值
//先得到第一個字節的有效位數據
tmp_wchar = src_str[0] & byte_one;
for (int i=1; i<count_bytes;i++)//原文這裏不完整,補充了
{
tmp_wchar <<= 6; // 左移6位後與後續字節的有效位值"位或"賦值
tmp_wchar = tmp_wchar | (src_str[i] & byte_other);//先與後或
}
// 位值析取__End!
dest_wchar = tmp_wchar;
return count_bytes;
}
友情感謝:
http://www.magicwolf.cn/hobby/utf-8-to-unicode.html
http://blog.sina.com.cn/s/blog_4d25c9870100chmu.html
http://blog.sina.com.cn/s/blog_697414470100qkgm.html
Java的class文件採用utf8的編碼方式,Java的字符串是unicode編碼的。
什麼是 Unicode?
如果把各種文字編碼形容爲各地的方言,那麼Unicode就是世界各國合作開發的一種語言。
在這種語言環境下,不會再有語言的編碼衝突,在同屏下,可以顯示任何語言的內容,這就是Unicode的最大好處。那麼Unicode是如何編碼的呢?其實非常簡單。
就是將世界上所有的文字用2個字節統一進行編碼。可能你會問,2個字節最多能夠表示65536個編碼,夠用嗎?
韓國和日本的大部分漢字都是從中國傳播過去的,字型是完全一樣的。
比如:“文”字,GBK和SJIS中都是同一個漢字,只是編碼不同而已。
那樣,像這樣統一編碼,2個字節就已經足夠容納世界上所有的語言的大部分文字了。
Unicode的學名是"Universal Multiple-Octet Coded Character Set",簡稱爲UCS。
unicode包含兩個概念,一是字符集,二是編碼方式。utf-8是unicode的其中一種編碼方式。
UCS 和 Unicode 只是分配整數給字符的編碼表. 現在存在好幾種將一串字符表示爲一串字節的方法. 最顯而易見的兩種方法是將 Unicode 文本存儲爲 2 個 或 4 個字節序列的串. 這兩種方法的正式名稱分別爲 UCS-2 和 UCS-4. 除非另外指定, 否則大多數的字節都是這樣的(Bigendian convention). 將一個 ASCII 或 Latin-1 的文件轉換成 UCS-2 只需簡單地在每個 ASCII 字節前插入 0x00. 如果要轉換成 UCS-4, 則必須在每個 ASCII 字節前插入三個 0x00.
在 Unix 下使用 UCS-2 (或 UCS-4) 會導致非常嚴重的問題. 用這些編碼的字符串會包含一些特殊的字符, 比如 '\0' 或 '/', 它們在 文件名和其他 C 庫函數參數裏都有特別的含義. 另外, 大多數使用 ASCII 文件的 UNIX 下的工具, 如果不進行重大修改是無法讀取 16 位的字符的. 基於這些原因, 在文件名, 文本文件, 環境變量等地方, UCS-2 不適合作爲 Unicode 的外部編碼.
UTF-8 有一下特性:
-
UCS 字符 U+0000 到 U+007F (ASCII) 被編碼爲字節 0x00 到 0x7F (ASCII 兼容). 這意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 兩種編碼方式下是一樣的.
-
所有 >U+007F 的 UCS 字符被編碼爲一個多個字節的串, 每個字節都有標記位集. 因此, ASCII 字節 (0x00-0x7F) 不可能作爲任何其他字符的一部分.
-
表示非 ASCII 字符的多字節串的第一個字節總是在 0xC0 到 0xFD 的範圍裏, 並指出這個字符包含多少個字節. 多字節串的其餘字節都在 0x80 到 0xBF 範圍裏. 這使得重新同步非常容易, 並使編碼無國界, 且很少受丟失字節的影響.
-
可以編入所有可能的 231個 UCS 代碼
-
UTF-8 編碼字符理論上可以最多到 6 個字節長, 然而 16 位 BMP 字符最多隻用到 3 字節長.
-
Bigendian UCS-4 字節串的排列順序是預定的.
-
字節 0xFE 和 0xFF 在 UTF-8 編碼中從未用到.
Unicode與UTF-8之間的轉換
“嚴”的Unicode碼是4E25,UTF-8編碼是E4B8A5,兩者是不一樣的。它們之間的轉換可以通過程序實現。
在Windows平臺下,有一個最簡單的轉化方法,就是使用內置的記事本小程序Notepad.exe。打開文件後,點擊“文件”菜單中的“另存爲”命令,會跳出一個對話框,在最底部有一個“編碼”的下拉條。
1)ANSI是默認的編碼方式。對於英文文件是ASCII編碼,對於簡體中文文件是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會採用Big5碼)。
2)Unicode編碼指的是UCS-2編碼方式,即直接用兩個字節存入字符的Unicode碼。這個選項用的little endian格式。
3)Unicode big endian編碼與上一個選項相對應。我在下一節會解釋little endian和big endian的涵義。
4)UTF-8編碼,也就是上一節談到的編碼方法。
需要在哪些時候注意編碼問題?
1. 從外部資源讀取數據:
InputStream ins = new FileInputStream("Test.java");
InputStreamReader instrd= new InputStreamReader(in, "GB2312");
//我們採用了GB2312編碼讀取外部數據,通過查看streamReader的encoding可以印證:
assertEquals("GB2312", instrd.getEncoding());
2. 字符串和字節數組的相互轉換:
我們通常通過以下代碼把字符串轉換成字節數組:
"string".getBytes(); 等價於 "string".getBytes(Charset.defaultCharset());
如何從字節數組創建一個字符串呢:
new String("string".getBytes());