關於編碼的一切

亂碼時間格式錯誤如同矗立在所有程序員面前的太行王屋兩座大山。

編碼錯誤或亂碼幾乎是每個人都會遇到的問題,比如用 Python 抓取網頁進行正則分析之後發現在原來網頁上正常的文字抓取下來之後變成亂碼了,或者是存進 MySQL 之後變成亂碼了,又或者本來在編輯器裏面編輯得好好的輸出之後又是亂碼……要命的是每次遇到亂碼問題,經過一番痛苦掙扎之後終於解決了,之後總是還會遇到新的亂碼問題,這纔是真正的“子子孫孫無窮盡也”,本文試圖從頭到尾將編碼問題徹底闡述清楚,至少以後再次遇到燙燙燙燙燙燙燙燙燙的問題能夠有跡可循。

1. 什麼是編碼

中文  翻譯成英文變爲 rain ,我們可以說是將中文這種以橫豎撇捺等簡單筆畫爲基本元素的編碼形式轉換成了英文這種以 a-z 26個英文字母爲基本元素的編碼形式,同樣在計算機的世界裏面,一切事物都是由 0 和 1 這兩個基本元素編碼而成的,想象一下顯示器上面所呈現出來的所有的精美絕倫的影像以及音響裏傳出的所有美妙樂章都源自於 0 和 1 這兩個簡單數字的複雜組合。

編碼問題從理論上來說也許並不複雜,只是從一種符號系統到另外一種符號系統之間的映射,但是在實際操作或者實施編碼的過程中往往會出現很多問題(見第4部分),我們需要將人類的意圖翻譯成機器能夠理解的二進制編碼,在計算機內部經過一系列的加工處理之後,又將這些二進制的信息重新編碼成人類可以理解的自然語言(中文或者是英文甚至是圖像、影音),對於英文來說,由於其語言構成的規律特性,只需要將其每一個元素(字母或簡單符號)與二進制數進行一一對應,然後按照順序排列就可以得到相應的單詞、語句,因而簡單的ASCII編碼系統已經足夠應付,然而爲了表示更多的語系或符號,我們就不得不 ASCII編碼系統進行擴充,以容納或者表示更多的語言符號,而亂碼的問題也大多產生與此,接下來繼續看一下計算機是如何對更多的語言符號進行編碼的。

2. 如何編碼

很巧的是編碼格式與時間格式等問題以及許多計算機相關的問題都避免不了與格式相關,統一的格式源自於相同的規則或者說標準(Standard)。ASCII第一次以規範標準的型態發表是在1967年,最後一次更新則是在1986年,至今爲止共定義了128個字符(Wiki),在ASCII編碼中每一個字符需要8位來表示,而8位一共可以編碼2**8 = 256個字符,這顯然不足以將全世界各種不同語系、各種不同的符號涵蓋在內,因而就需要將其進行擴展,也就是我們現在最常用的Unicode,現在 Unicode 仍然在不斷增加,每次增加都會有新的字符被編碼進來,Unicode目前普遍採用的是UCS-2,它用兩個字節來編碼一個字符,每個字符佔用2個字節,這樣理論上一共最多可以表示2**16 = 65536個字符,其中中文在Unicode表中所對應的二進制數字(轉換成十六進制後)的範圍是:4E00 - 9FFF中日韓統一表意文字(CJK Unified Ideographs)。ASCII 與 Unicode 的編碼方式就如同 C 語言中 char 類型與 int 類型的區別一樣。

3. Unicode 與 UTF-8

Unicode是一種編碼標準,比如說它規定漢字對應的數字大小爲0x96e8(十進制38632),因而從本質上來說Unicode只是一張字符表(code table),但是具體用什麼方法來實現這種編碼的轉換,也有許多不同的選擇,常見的UTF-8(8-bit Unicode Transformation Format)即是其中一種轉換方法。(UTF-8 and Unicode FAQ),其工作方式如下圖所示:

UTF-8

4. 實現過程中的各個環節

字符從鍵盤輸入到顯示器顯示出來,一般會經歷下面幾個過程:

encoding-decoding-flow

  • 首先是編碼過程,這一過程與所用的編輯器相關,以 vim 爲例,當以 ASCII 的格式保存文件時,如果文件中含有 ASCII 字符之外的字符,則在保存的時候就會報錯,因而需要將 vim 設置爲 UTF-8 的編碼格式(以UTF-8的方法保存、讀取文件),方法如下:

    #file ~/.vimrc
    set fileencoding=utf-8
    

    大多數編輯器中出現亂碼的問題多是由於保存格式的原因所導致,因此一般可以通過改變配置中的編碼格式(如設爲UTF-8)修復。

  • 對程序員來來說更關心的是在特定編程環境下,字符的編碼是如何被加工處理的,下面就以 Python 2.7.5 爲例加以解釋。Python 2.x 中默認的編碼格式爲 ASCII:

    import sys
    sys.getdefaultencoding()
    //Output >>> 'ascii'
    

    UTF-8的編碼方式規定,Unicode範圍由U+0800至U+FFFF的字符(如)使用三個字節進行編碼,其編碼方式如下:

    U+00000800 – U+0000FFFF <-> 1110xxxx 10xxxxxx 10xxxxxx
    

    如此一來,我們便知道會出現'\xe9\x9b\xa8'這種奇怪的亂碼的原因是什麼了:

    rain = '雨'
    rain
    //Output >>> '\xe9\x9b\xa8'
    
    rain.decode('utf8')
    //Output >>> u'\u96e8'
    
    rain.encode('utf8')
    //Raise error:
    """
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        UnicodeDecodeError: 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)
    """
    

    出現上面編碼錯誤的原因還要從UTF-8的編碼方法中找,UTF-8使用3個字節對字進行編碼,得到的Unicode碼爲應爲'\u96e8',而其對應的UTF-8數字卻爲'e99ba8'(見上面UTF-8的編碼方式中的xxxx),而這三個字節在 ASCII 中則被表示爲'\xe9\x9b\xa8',因此這一編碼重新以UTF-8進行解碼rain.decode('utf8')仍然可以得到正確的Unicode,但是在ASCII環境下進行UTF-8編碼則會報錯,因爲已經超出了ASCII的編碼範圍。解決這一問題的方法就是設定默認的編碼方式爲UTF-8:

    import sys
    reload(sys)'''Python 運行時會檢查環境設置並刪除setdefaultencoding方法,因而需要reload(sys)'''
    sys.setdefaultencoding('utf8')
    
  • 最後一步是關於解碼的過程,即將計算機存儲的二進制數轉換成屏幕上顯示出來的各式各樣的字符,這一過程中產生的亂碼一方面可能是由於系統不支持對某些字體的渲染甚至對於較舊的系統不支持某些特殊字符的顯示,另一方面仍然可能是所配置的編碼格式的問題,嘗試將編碼格式設置爲UTF-8或許解決問題。

總結

本文試圖理清計算機中關於編碼的一系列概念,如 ASCII、Unicode、UTF-8等,並試圖從編碼及解碼的各個層面找出可能出現亂碼的原因,爲以後能夠更快地定位亂碼錯誤原因提供參考。

- END -
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章