關於字符集,編碼格式,大小端的簡單總結

只要你和計算機打交道,這些問題能夠說是每天會遇到,可是不少人是似懂非懂, 能真正徹底理解的人卻很少, 下面是我的的一些理解,有錯歡迎指正. html


最先的計算機只支持ASCII碼, 具體來講就是用1個字節(最高位爲0, 沒有用)表示0到127,總共128個字符, 這樣就能夠徹底知足英語應用的要求了。

後來擴展到歐洲語系,理論上一個字節能夠表示256個字符, 歐洲人就把剩餘的128個字符(最高位爲1)按照本身語言(法語,德語...)的要求擴充應用了起來, 好像也能知足須要。

而後又到了亞洲國家,好比中國,中文漢字有十多萬,這剩餘的128個字符根本不夠我用啊, 怎麼辦? 因而就有了兩個字節的編碼,如中文的GBK, GB2312, BIG5等,固然日語,韓語等其餘亞洲國家也有本身編碼方式。

這就是所謂的多字節編碼(MBCS)方式, Win95/98時代只支持這種方式, 那時候處理字符串很是痛苦, 由於它裏面有些字符是一個字節表示的,也有一些是多個字節表示的, 好比字符串"你好abc", 裏面明明是5個字符,strlen返回長度倒是7, 你要正確識別字符個數,可使用相似_mbslen的API, 可是實際上該API內部會綁定當前的字符集, 否則神仙也識別不了。

要統一解決上面的問題, 須要有一個世界通用的統一編碼格式, 那就是UNICODE。
 UNICODE我的感受分廣義和狹義, 廣義的UNICODE包括UTF8, UCS2, UCS4, 而狹義的UNICODE(主要是Windows平臺)就是指UCS2。
先說UCS2, Windows平臺上常說的UNICODE實際上就是指UCS2, 簡單來講就是統一用2個字節的編碼,表示實際上全部語言的經常使用字符。
再說UTF8, 有了上面的UCS2,爲甚麼還有要UTF8? UCS2把任何字符全都編碼成2個字節(包括咱們經常使用的英文字符), 這樣極大地增長了網絡傳輸和數據存儲的開銷,因而就有了UTF8。UTF8對英文字符仍是1個字節存儲,只對其餘語言字符用多個字節存儲(2-6個字節)。UTF8和UNICODE能夠徹底對應的相互轉換, 具體能夠參考這裏
爲何還要有 UCS4? UCS2用2個字節,最多也只能表示0xFFFF+1 = 65536個字符, 可是咱們僅漢字就有十多萬,因此UCS2/UTF8收錄的也只是咱們一些經常使用的漢字, 因此咱們須要UCS4, 用4個字節表示一個字符,能夠表示0xFFFFFFFF+1=4294967296個字符, 它纔是咱們之後的終極解決方案。

在Windows上不一樣編碼方式的相互轉換能夠經過WideCharToMultiByteMutiByteToWideChar進行, 它裏面WideChar就是指UCS2, 能夠看到它這裏把UTF8也命名成MultiByte, 是否是有點誤導...

下面再談小大小端(little-endian, big-endian).

計算機是以字節爲尋址單位的,這就涉及到字(2個字節), 雙字(4個字節)及其餘多字節單位 在計算機內如何排布的問題, 這裏無非就是2種:低字節在低地址的little-endian和高字節在低地址的big-endian.
如何區分當前系統是哪一種類型的大小端? 曾經看到有經驗的程序員也以當前的操做系統類型來判斷, 實際上系統的大小端和你的CPU架構體系相關聯, 好比說X86是小端, PowPC是大端,ARM則是可控制(默認也是小端)。
要判斷當前環境是大小端實際上很簡單: bool IsLittleEndian()  {  int i=1;  return (*(char *)&i == 1); }
曾經看到公司跨平臺的代碼沒有經過大小端轉換,直接經過memcpy某個表示長度的int在客戶端之間傳送,卻沒有發生問題, 感受很奇怪, 最後發現原來當前公司的全部客戶端(Win, Mac, ios, Android,BlackBerry,Linux)全都是小端。感受如今大端的應用主要是網絡字節序, Java內部全都是大端。
上面的UCS2和UCS4由於都是用多字節表示一個字符, 因此實際上都有大小端的問題,好比分別對應UCS2-LE和UCS2-BE,Windows上的UNICODE其實是UCS2-LE, UTF8由於是字節流,因此沒有大小端的問題。

下面再說一下BOM (Byte Order Mark), 上面說了各類編碼方式以及大小端的問題, 那麼咱們怎麼知道某個文本或者數據流是何種編碼方式? 
通常來講有3種方法:一種是分本顯示指定, 好比web裏html頭通常會有這麼一段"content="text/html;charset=utf-8"; 要不就是你們默認約定,好比自定義的網絡數據流內的字符串通常都會用UTF8編碼; 還有一種就是用BOM,經過在文件頭裏填入BOM規定的字節,從而區分文件是何種編碼類型: 

UTF-8 0xEF 0xBB 0xBF
UTF-16 BE 0xFE 0xFF
UTF-16 LE 0xFF 0xFE
UTF-32 BE 0x00 0x00 0xFE 0xFF
UTF-32 LE 0xFF 0xFE 0x00 0x00

有興趣的同窗能夠用notepad保存,試下各類效果, 而後用UltraEdit的16進制方式查看驗證。

最後討論下C++編程中常見的關於字符編碼方式相關的問題。

在C++編程中, 咱們常打交道的無非是編輯器和編譯器, 對編輯器起來講,咱們常遇到就是亂碼問題, 好比中文註釋顯示或是保存不了等, 解決辦法就是把你的文件保存成Unicode(UTF8)。
對於編譯器來講, 編碼方式取決於它對C++標準的支持程度, 好比C++ 11之前,字符串咱們只能指定成2種:一種是MBCS,如char* p="abc哈哈"; 還有一種是UCS2, 好比wchar_t*p = L"abc哈哈", 這樣編譯器就知道你要表示的字符串類型。C++11以後,標準增長了UTF8和UCS4的支持, 好比char* p=u8"abc哈哈"表示UTF8,wchar_t * p=u"abc哈哈"表示UCS2(實際上和L"xxxx"同樣),  char32_t* p=U"abc哈哈"表示UCS4。這裏要區分編譯期和運行期, 儘管C++11以前咱們無法告訴編譯器咱們這個常量串是UTF8格式的,可是程序期咱們仍是可使用全部的編碼式 (MBCS/UTF8/UCS2/UCS4), 由於這些最終在內存裏都是二進制流。
另外C++11還增長了UTF8, UCS2, UCS4相互轉碼的支持:
std::codecvt_utf8 封裝了UTF8相關的編碼轉換
std::codecvt_utf16 封裝了UCS2相關的編碼轉換
std::codecvt_utf8_utf16 封裝了UTF8與UCS2的編碼轉換

對於C++跨平臺開發, 咱們常常遇到的就是默認用那種編碼方式的問題,咱們會發現Windows 的UCS2解決方案對其餘平臺來講是個異類, 通常來講有2種解決方法:
一種是統一用UTF8 , 可是這樣對Windows來講有點麻煩, 由於Windows的API都是UCS2的,因此這種方式意味着任何字符串在傳給Windows API 以前都要從UTF8轉成UCS2; 還有一種就是用#define宏了, Windows上將字符串相關宏全都定義成UCS2, 其餘平臺則全都定義成UTF8, 該方式要求就你在寫代碼時頭腦要比較清醒,由於一樣的代碼在不一樣平臺上的編碼格式是不同的。

一直很好奇,誰知道Windows爲何不用UTF8,非要搞得和其餘平臺不同?
相關文章
相關標籤/搜索