聊聊字元編碼

oscarwin發表於2019-01-23

你是否有過這樣的經歷,檔案在別人電腦上看著好好的,但是拷貝到自己的電腦上就發現亂碼了。或者作為程式設計師的你,開啟別人的程式碼時,發現中文註釋亂碼了,而程式碼缺一點毛病都沒有。我曾經也被這些問題困擾許久,雖然很多時候轉換一下字元編碼問題就解決了,但是背後的原因卻一直縷不順。因此,想借這篇文章,儘可能把這件事情給說清楚。

字元編碼的起源-ASCII碼

計算機儲存資料都是以二進位制的形式進行儲存的,但是二進位制形式人類是無法直接解讀的。因此,對於儲存在計算機上的文字資料,需要一張對映表實現二進位制與文字之間的相互轉換。比如,可以約定0000 0001代表字母A,0000 0002代表字母B,因此當你儲存一段文字ABA時,實際上計算機儲存的是0000 0001 0000 0002 0000 0001。相應的,當你開啟這個檔案時,本質讀取的二進位制資料,但是編輯器會將這段二進位制碼查錶轉換成ABA

計算機問世的早期,有這樣一張對映表被建立作為編解碼的標準,稱為ASCII碼。

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編碼,由0x6c0x49組成。用兩個位元組表示,那麼就有先後順序的問題,6c49496c兩種方式都能表示,因此兩種不同順序的編碼的編碼方式稱為大端和小端模式

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的基礎上進行擴充套件的,所以簡體中文用這兩者是相同的編碼。

相關文章