你是否有過這樣的經歷,檔案在別人電腦上看著好好的,但是拷貝到自己的電腦上就發現亂碼了。或者作為程式設計師的你,開啟別人的程式碼時,發現中文註釋亂碼了,而程式碼缺一點毛病都沒有。我曾經也被這些問題困擾許久,雖然很多時候轉換一下字元編碼問題就解決了,但是背後的原因卻一直縷不順。因此,想借這篇文章,儘可能把這件事情給說清楚。
字元編碼的起源-ASCII碼
計算機儲存資料都是以二進位制的形式進行儲存的,但是二進位制形式人類是無法直接解讀的。因此,對於儲存在計算機上的文字資料,需要一張對映表實現二進位制與文字之間的相互轉換。比如,可以約定0000 0001
代表字母A,0000 0002
代表字母B,因此當你儲存一段文字ABA
時,實際上計算機儲存的是0000 0001 0000 0002 0000 0001
。相應的,當你開啟這個檔案時,本質讀取的二進位制資料,但是編輯器會將這段二進位制碼查錶轉換成ABA
。
計算機問世的早期,有這樣一張對映表被建立作為編解碼的標準,稱為ASCII碼。
如上圖所示,ASCII碼用7個bit位表示,範圍從0~127,一共128個字元。在計算機問世的早期,主要只有英文符號,而且儲存空間十分有限,因此用一個位元組就能夠滿足需求了。但是隨著計算機的快速普及,各國都有將本國文字進行輸入輸出的需求,但是ASCII碼無法滿足這樣的需求,因此需要新的編碼。百花齊放-多字符集
對於拉丁語系國家而言,他們可以擴充套件ASCII碼的第8個bit位來滿足本國的編碼需求,但是對於非拉丁語系的國家而言,單字符集(只用了一個位元組完成編碼的稱為單字符集,對應的就是多個位元組編碼的成為多字符集)無法滿足編碼本國語言的需求,因此他們用多個位元組來表示一個文字。例如,我們國家就是使用的雙位元組字元編碼,使用最為廣泛的就是GB2312編碼。但是GB2312只能編碼簡體中文,因此出現了GBK編碼,GBK編碼除了支援簡體中文,還支援繁體中文以及日語、韓語等的編碼,是大一統的編碼。
GBK的編碼規則 GBK編碼使用1-2個位元組進行編碼,GBK編碼分為很多個碼頁,每個碼頁的範圍編碼的範圍都是一個位元組,即0x00-0xFF。GBK編碼先通過第一個位元組查詢第一張表(如下圖)。
如果第一個位元組的首位為0,也就是範圍在0x00-0x80之間時,直接從該表中查詢得到對應的字元,和傳統的ASCII碼查詢的方式相同。例如百分號%
的編碼就是0x25。
如果第一個位元組的首位為1,也就是範圍在0x81-0xFF之間時,先查詢該表得到第二個位元組需要查詢的編碼頁的頁碼號。例如漢字丏
的編碼為8144
,第一個位元組先查詢到要查第0x81編號的頁碼,然後在0x81編號的頁碼中查詢編碼為0x44的對應的文字就是丏
。
由於各國都有自己的編碼,而且編碼的方式還很多,規則的不統一導致,文字轉化中會很麻煩,因此制定了ANSI標準,各國指定標準的多字符集編碼方式,例如我國的標準編碼就是GB2312。
全球一統-UNICODE
由於各國都制定了本國的多位元組的字元編碼,導致全球範圍內的字元字元編碼集非常多,這會使得各國之間文字轉換非常的麻煩。因此大佬們坐下約定了一個全球統一的編碼,用一個編碼表示全世界所有的文字的,這就是UNICODE編碼。UNICODE編碼是兩個位元組,因此可以編碼256*256個字元,這基本可以滿足全球字元編碼的需求了。
我們以漢字的漢
為例,其Unicode編碼為\u6c49
,\u
用來標識其為Unicode編碼,由0x6c
和0x49
組成。用兩個位元組表示,那麼就有先後順序的問題,6c49
和496c
兩種方式都能表示,因此兩種不同順序的編碼的編碼方式稱為大端和小端模式
Unicode實際上還是一個比較廣義的概念,在實際編碼規則中常見的有UCS-2,UTF-16,UTF-8。接下來我們分別闡述這幾種編碼的概念。
UCS-2與UTF-16
UCS-2是Unicode編碼的標準實現,所有的字元都是按照兩個位元組編碼。兩個位元組的順序不同就產生了USC-2大端和USC-2小端兩種模式。但是UCS-2只編碼了BMP字元,而UTF16則常用變長的方式來相容其他字元,最短兩個字元。BMP字元UTF16和UCS-2是相同的,擴充套件的部分則用四個位元組編碼。
UTF-8
終於到了我們最熟悉的UTF-8了。Unicode編碼出現後,使用拉丁文的國家發現自己吃了大虧,他們認為拉丁文原本只需要一個位元組就可以編碼,現在卻需要兩個位元組,因此搞出了變長的UTF-8字元。
UTF-8的編碼規則
[1] UTF-8是變長度的,長度為1-6個位元組;
[2] 第一個位元組的連續的二進位制位值為1的個數決定了其編碼的位數
[3] 如果第一個位元組以0開頭說明是單個位元組的字元
[4] 對於非單個位元組的字元,出首位元組外,其餘均已10開頭
上面的規則用編碼表示更加清晰,如下:
佔用位元組 | 首位元組大小 | 完整表示 |
---|---|---|
1位元組 | 大於0 | 0xxxxxxx |
2位元組 | 大於0xc0 | 110xxxxx 10xxxxxx |
3位元組 | 大於0xe0 | 1110xxxx 10xxxxxx 10xxxxxx |
4位元組 | 大於0xf0 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
5位元組 | 大於0xf8 | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
6位元組 | 大於0xfc | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
根據這個編碼規則,就可以判斷出一個字串是由多少個字元組成,C++程式碼如下:
inline std::string GetHideName(const std::string& sUtf8Data)
{
std::vector<std::string> vName;
std::string ch;
for (size_t i = 0, len = 0; i != sUtf8Data.length(); i += len)
{
unsigned char byte = (unsigned)sUtf8Data[i];
if (byte >= 0xFC) // lenght 6
len = 6;
else if (byte >= 0xF8)
len = 5;
else if (byte >= 0xF0)
len = 4;
else if (byte >= 0xE0)
len = 3;
else if (byte >= 0xC0)
len = 2;
else
len = 1;
ch = sUtf8Data.substr(i, len);
vName.push_back(ch);
}
std::string sQxName;
if (vName.size() <= 2)
{
sQxName = vName.size() > 0?(vName.front() + "*"):sUtf8Data;
}
else
{
sQxName = vName.front() + "**" + vName.back();
}
return sQxName;
}
複製程式碼
問題
[1] GBK與GB2312的區別? GBK是GB2312的超集,你可以簡單這樣理解,GB2312編碼的是簡體中文,而GBK在GB2312的基礎上增加了繁體字以及日語和韓語。因為GBK是在GB2312的基礎上進行擴充套件的,所以簡體中文用這兩者是相同的編碼。