Unicode、ANSI、UTF-8、GBK詳談
最近在寫網路通訊上的一些東西,快被這些編碼格式搞崩潰了。
一、什麼是編碼
編碼是對現有“符號”進行轉化,可以儲存在計算機中,在沒有計算機時,我們的使用的“符號”,都是手寫的,我們的大腦對其編碼,這樣我們就能記住和識別。但計算機只能儲存電訊號,即二進位制。所以,我們需要對其編碼,能使計算機儲存。
各個國家和地區所制定了不同 ANSI 編碼標準中,都只規定了各自語言所需的“字元”。這樣就不利於交流,所以就有了Unicode編碼。
名詞解析:
1.ANSI:眾所周知,剛開始計算機是在美國出現的,所以他們也是第一批考慮編碼的,第一開始由於只是用於計算,所有,只用了ASCII碼,但後來計算機飛速發展,已經不僅僅用於計算。所以美國就提出了ASNI標準來進行編碼。後來,中日韓等又提出了自己的編碼編碼,例如中國的GBK,但還是統稱為ANSI標準。因此它是一個編碼集,並不是特定指一種編碼格式。
2.Unicode:這個又稱萬國碼,設計的初衷就是設計出一種通用的編碼集。
3.UTF-8:這是Unicode的一種實現方式。Unicode設計初衷是好的,但是由於是萬國碼,所以要用4個位元組來進行編碼,但是,如果使用Unicode編碼,儲存英文字元和數字時,就會浪費儲存空間,因此需要使用一種能減少儲存空間的方法。UTF-8就應運而出。
二、utf-8具體實現。
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
其中x填入的即是對應的Unicode編碼。
UCS-2和UCS-4
Unicode是為整合全世界的所有語言文字而誕生的。任何文字在Unicode中都對應一個值,這個值稱為程式碼點(code point)。程式碼點的值通常寫成 U+ABCD 的格式。而文字和程式碼點之間的對應關係就是UCS-2(Universal Character Set coded in 2 octets)。顧名思義,UCS-2是用兩個位元組來表示程式碼點,其取值範圍為 U+0000~U+FFFF。
為了能表示更多的文字,人們又提出了UCS-4,即用四個位元組表示程式碼點。它的範圍為 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一樣的。
要注意,UCS-2和UCS-4只規定了程式碼點和文字之間的對應關係,並沒有規定程式碼點在計算機中如何儲存。規定儲存方式的稱為UTF(Unicode Transformation Format),其中應用較多的就是UTF-16和UTF-8了。
三、怎麼對編碼進行轉換。
首先要明確一個概念,由於網路中大部分都是利用utf-8進行編碼的(這是因為utf-8通用,且同樣的內容所佔位元組更少)。我們所謂的轉換編碼,大部分是轉換到utf-8利於傳輸,但utf-8仍然屬於Unicode編碼,所以就會有這樣一個邏輯關係。
ANSI(GBK)需先轉換到 Unicode,從Unicode在轉換為utf-8,反之亦然。
首先是ANSI轉換為Unicode:這一步,是我剛開始最不理解的。因為底層編碼都是不同。利用漢字“嚴”。其Unicode編碼為 4E25,但其GBK 編碼為:D1CF。查了一點資料,發現和自己想的一樣,是利用對應的編碼表轉換。
其次是Unicode轉換為UTF-8。這一步,瞭解到具體的UTF-8的構成,我們就可以很容易搞懂其具體原理。
四、我所找到的函式
宣告:以下函式均摘自別人部落格。
1.UTF-8和GBK互轉。
這與我前面所述,並無衝突。只是隱含了轉到Unicode這一過程。這是利用 WindowsAPI來實現的。
傳送門:
https://blog.csdn.net/xiaohu_2012/article/details/14454299
#include<windows.h> #include<string> std::string UTF8ToGBK(const char* strUTF8) { int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0); wchar_t* wszGBK = new wchar_t[len + 1]; memset(wszGBK, 0, len * 2 + 2); MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len); len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); char* szGBK = new char[len + 1]; memset(szGBK, 0, len + 1); WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); std::string strTemp(szGBK); if (wszGBK) delete[] wszGBK; if (szGBK) delete[] szGBK; return strTemp; } std::string GBKToUTF8(const char* strGBK) { int len = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0); wchar_t* wstr = new wchar_t[len + 1]; memset(wstr, 0, len + 1); MultiByteToWideChar(CP_ACP, 0, strGBK, -1, wstr, len); len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); char* str = new char[len + 1]; memset(str, 0, len + 1); WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); std::string strTemp = str; if (wstr) delete[] wstr; if (str) delete[] str; return strTemp; }
這裡利用的是Windows API:
MultiByteToWideChar()
WideCharToMultiByte()
需要說明的vs編輯問題。https://blog.csdn.net/a3192048/article/details/82154194
工程屬性裡,字符集可以選擇“使用Unicode字符集”和“使用多位元組字符集”。此選項只控制程式碼裡的API是用寬字元版(即Unicode)的還是ANSI字元版(即GBK)的,它控制不了程式碼裡的字元是用Unicode編碼還是ANSI編碼。
如果選擇了“使用Unicode字符集”,則程式碼裡用到的API被解釋為Unicode版本的API(帶標記W的API),如MessageBox被解釋為MessageBoxW;
如果選擇了“使用多位元組字符集”,則程式碼裡用到的API被解釋為ANSI編碼版本的API(帶標記A的API),如MessageBox被解釋為MessageBoxA。
這裡真是糾結半天,其實我們編寫的內碼表仍然是系統預設的編碼集,Windows一般是gbk。還有你在vs中設定儲存為 utf-8時,會發現你不能在程式中,使用漢字字串。這也是vs最坑爹的地方。
2.UCS-2轉utf-8。這就是Unicode轉utf-8。
傳送門:https://blog.csdn.net/go_to_learn/article/details/8048472
五、我的感悟
寫了一個傳輸utf-8的簡單通訊,這個編碼就能困擾我兩天。c++對這些底層編碼真是沒有現成的庫。不像Java那樣封裝好了。Java中一行程式碼就可以搞定的事,我自己折騰了好久。不過也真正懂得了編碼的概念。以前經常混淆。
碰壁之處:
我的傳輸資料中有漢字,需要轉碼成utf-8進行傳輸,否則伺服器會識別不出來我傳輸的資料。由於字元和英文屬於127的字元。Unicode和GBK都是相同的。不存在轉碼問題。只有漢字需要轉碼。
解決方法:
在封裝資料包時,將漢字轉碼為utf-8格式。由於 特殊字元和英文在utf-8和gbk中都相同。所以我們封裝的資料包就相當於utf-8的資料包,雖然我們只轉碼了漢字。這樣伺服器就可以正常解析了。