字元編碼(一)|編碼基本說明

使用python的過程中,或多或少都會接觸一些字元編碼的問題,網上提到比較多的是python2中會有各種奇怪的編碼問題,很多人都會說換成python3之後就沒有編碼的煩惱了,但事實上不是這樣的。只能說python2因為自身設計問題產生了更多編碼的問題,不能說換用python3之後就能徹底擺脫編碼煩惱了。既然因為歷史原因,產生了這麼多種編碼,人們使用過程中也不能保持一致,那麼編碼問題將會一直存在下去。要想不被編碼問題困擾,最好的方法就是徹底了解其背後的機制。

雖然編碼問題網上已經有了大量文章,但是沒有一篇文章能把所有的問題涵蓋,所以我寫了這個系列。我看了網上非常多關於字元編碼的博客和回答,結合自己的使用,儘可能全地總結平時可能遇到的編碼問題。有些普遍的問題很多博客都說的很清楚了我就貼上鏈接再簡單敘述一下了。

這個系列分為以下幾個部分

  • 編碼問題的起源(文1)
  • 常見字元編碼(文1)
  • 文件保存與打開中的編碼問題(文1)
  • python3中的編碼與解碼原理(文2)
  • python3中的報錯或亂碼(文2)
  • 與文件網頁交互時的報錯或亂碼(文3)
  • python2中的奇特編碼問題(文4)

本文包括前三個部分

  • 編碼問題的起源
  • 常見字元編碼
  • 文件保存與打開中的編碼問題

編碼的起源

幾乎每一篇講編碼的文章都會說一遍編碼的起源,詳細的在這裡就不多說了,可以參考下面鏈接

  • 廖雪峰-字元串和編碼
  • python之禪-Python編碼錯誤的本質原因

總結起來就是

  • 計算機只能計算數字,為了表示字元,使用二進位數來對應字元進行存儲,這種對應即所謂的編碼
  • 最初只有ASCII編碼,只包含英文字母和一些符號共128個
  • 為了能用計算機表示中文,中國人制定了GB2312編碼。同時各個國家都為自己的語言制定了一套編碼
  • 因為每個國家制定的編碼無法兼容(比如在計算機中相同的二進位數在各個國家的編碼中表示的字元不一樣,從而產生亂碼),所以最後統一出了一套Unicode標準
  • 而Unicode比較占空間,於是產生了「可變長編碼」的UTF-8編碼,這時當前最通用的編碼

常見編碼

這裡介紹一下我們平常會經常見到的編碼,了解了這些常見編碼,在遇到時就大概知道哪些編碼是做什麼的,比如下面文本編輯器sublime中reopen可以使用的編碼表

1.ASCII編碼

用8個比特(一個位元組),即8位的二進位數來表示一些符號。其中包含了26個英文字母大小寫字母、0-9數字,以及鍵盤上能看到的!@#$%^&*()_+{}|<>?等符號。因為8位之首一直是0,所以一共可以表示128個。

這是最初產生的編碼,所以之後的所有編碼幾乎都兼容這種

2.EASCII、ISO/8859-1、Latin-1、windows 1252

EASCII是ASCII編碼的擴展,將8個比特數值全部填滿,可以表示256個數,加入了一些公式符號、希臘字母等。不過EASCII並不表示某一種編碼方式,有很多種不同的擴充方式。ISO/8859-1是其中一種,又名Latin-1。windows 1252是windows對應設計的一種。

3.GBK、GB2312

為了表示中文字元,中國人最初指定了GB2312,可以表示絕大多數中文字元,但是仍有少部分不能表示。之後又擴充產生了完整表示中文字元的GBK編碼。GBK使用兩個位元組(16位二進位數)來表示字元。

在windows操作系統下,我們經常看到GBK這種編碼,很多軟體都默認用這個編碼來保存和讀取文件。比如記事本默認使用ANSI保存文件,常常就是使用GBK保存的文件。(ANSI後面會具體講)

4.Unicode

這是國際組織制定的全球統一編碼,可以表示任意字元,通常佔用兩個位元組。其實Unicode只是將每個字元對應一個數字使字元得到唯一標示,它沒有真正用於計算機存儲,真正根據Unicode設計用於存儲的是下面的UTF系列編碼。

5.UTF-8

UTF-8可以說是當前最推崇的一種編碼方式,可以表示任意字元,同時它一種「可變長」的編碼,即表示不同字元的位元組數量是不相同的。比如英文就延續ASCII使用一個位元組,中文是3個位元組,更不常用的字元可能是4-6個位元組。

經常和UTF-8一起提到的還有

  • UTF-16
  • UTF-16 LE
  • UTF-16 BE
  • 帶 BOM 的 UTF-8
  • 無 BOM 的 UTF-8

這裡只需要知道

  • UTF-8和UTF-16都是Unicode的一種實現方式
  • UTF-16是2個位元組(或少部分4位元組)的,因此英文使用的是2位元組。我們通常說UTF-8可變長度在存儲英文文件時比Unicode更省空間,應該說的是比UTF-16省空間。
  • UTF-16的LE和BE的區別其實只是一些地方的位元組順序不同,理解成兩種有細微差別的編碼方式即可
  • 帶BOM可以理解成保存時在最前面加一個標誌,讓軟體讀取文件時知道這個文件是用什麼進行的編碼,然後用相應方法解碼

這部分沒那麼重要,而且有點亂,更詳細的內容見下面這些鏈接

  • 有沒有BOM的更多差別可以看知乎的這個回答還有這個回答
  • 一篇比較完整闡述的文章

6.ANSI

這是windows記事本中特有的,記事本保存文本文件時默認使用的是ANSI,其看下面保存時的界面,可以有4種選擇

默認使用ANSI,但其實是使用GBK保存的。經常默認使用GBK而不是utf-8保存打開文件應該是windows被詬病的一個點(相比於mac和linux)。有的人就會問為什麼windows這麼反人類,不用兼容性好的utf-8,但是這有點冤枉windows了。現在的windows其實使用的就是unicode,但是很多軟體用的不是unicode,為了兼容那些軟體,windows設下這樣的規定:設置一個默認編碼,遇到一個字元串如果使用unicode就用unicode,不使用的就用這個默認語言來解釋。而這個默認語言在不同windows語言版本中是不一樣的,在簡體中文版是GBK,在日語的windows系統中就是支持日語的編碼。ANSI就是這個默認編碼,因此在這裡使用ANSI就相當於使用GBK。

更多內容參考這個回答和這個回答

關於記事本還要說明以下幾點

  • 其中UTF-8其實是帶BOM的,我們正常使用時最好使用不帶BOM的UTF-8。當我們在這裡選擇使用UTF-8保存時,會自動在整個文檔前面加一個標誌性的東西即BOM,之後再讀取時就可以知道這個是用UTF-8編碼的,從而使用UTF-8來解碼。帶BOM的UTF-8是windows為了讀取準確而設計的,兼容性不是十分好,所以不建議使用。
  • 如果使用ANSI保存,沒有BOM的標示,在打開文件時,記事本也無法判斷文件是用什麼編碼的,只能通過那些位元組進行猜測,所以有時會出現猜錯的情況。比如在記事本中輸入「聯通」兩個字,保存關閉,之後再打開,會發現亂碼了。是因為「聯通」二字的編碼比較像UTF-8的編碼,於是使用UTF-8來解釋GBK編碼的字元,肯定會出現亂碼。如果文件「聯通」後面還有很多字,關閉打開就可以正常顯示,因為打開時可以參照的文字變多了,就可以正確識別是用GBK編碼的了。記事本的更多bug見這個回答

本節這些編碼的更詳細解釋見這個回答

上面我們介紹了各種編碼如何演變以及常見編碼,之後幾個部分就來看一下哪些情況下會遇到亂碼問題。

文件保存與打開中的編碼問題

本節分為如下部分

  • 明確存儲原理
  • txt文件不同編碼方式保存與打開試驗
  • sublime中關於編碼的選項解釋
  • 如何知道一個文件的編碼
  • R文件亂碼問題
  • python腳本開頭提示

明確存儲原理

首先要明確,我們人眼看到的英文字母、文字等計算機是無法識別的,也無法直接處理和儲存這樣的內容,所以需要將這些字元編碼成計算機可以識別的二進位數進行存儲。

比如在記事本中輸入A中,使用UTF-8保存文件,記事本會將這些字元按照UTF-8的規則編碼成01000001 11100100 10111000 10101101這樣的二進位數,將這些數存到硬碟中。之後某一天我們想看文件中的內容,用記事本打開,它會將這一串二進位數又解碼為我們能看懂的字元串。因為在UTF-8下二進位編碼和字元是一一對應的,所以存儲和打開時都使用UTF-8就不會出現亂碼。

而如果我們讀取時使用EASCII,它的每個字元對應一個位元組,即上面存儲的4個位元組會被解碼成4個字元,顯然不是我們最開始保存的A中了,這種不一致就是亂碼。這段二進位數放在EASCII中可能得到的字元我們還認得,而在有的編碼方式中可能就對應著非常生僻的符號,全篇解碼出來都是這種看不懂的東西,這就是我們腦中最直觀的所謂亂碼了。

還可能你使用的解碼方式中沒有這段二進位數據對應的字元,此時就會報錯。因為一種編碼方式佔用一些位元組數,這些位元組可以表示的字元數量超過了當前需要表示的字元數量,就會有一些二進位數不對應任何字元,此時要將這些二進位數轉成字元而報錯。

txt文件不同編碼方式保存與打開試驗

首先推薦寫代碼時不要使用windows自帶的記事本,它對寫代碼的各種支持都不好。可以選擇自己稱手的文本編輯器,我用的是sublime text3,它一個優點是打開文件非常快。這個文本編輯器支持以各種編碼打開或保存文件,所以這裡拿這個編輯器對編碼問題進行測試。

為了更直觀地體會亂碼,這裡使用windows自帶的記事本和文本編輯器sublime text進行對比。

實驗方法:用記事本寫下一段中文保存,再用sublime text打開,看結果。這裡輸入寫下一段話很長的話再長一點

為了看使用不同編碼方式打開結果,用sublime打開後可以點擊 file-reload with encoding(沒有這個選項的看下一部分)來更換編碼方式,選擇GBK(中文簡體)就正常顯示,選擇BIG5(中文繁體)發現文本亂碼了,選擇CP932(日文)報錯了。

如下圖

sublime中關於編碼的選項解釋

1.選項說明。sublime打開一個文件使用什麼編碼方式也是靠猜,而保存一個文件默認方式是UTF-8。它本身是不支持GBK解碼的,剛下載sublime時應該是沒有reload with encodingset file encoding to的,只有reopen with encodingsave with encodingreopen with encoding讓我們可以在打開文件時指定用什麼方式解碼,但是這裡沒有GBK,要想解碼GBK就只好安裝ConvertToUTF8這個插件(如何安裝百度一下),它提供了reload with encoding選項,也是表示用什麼編碼解碼文件,這裡提供了簡體中文(GBK)、繁體中文、日文等編碼。reload和reopen的功能其實是一樣的,只是支持的編碼方式不一樣。

安裝好插件後,大多數時候用記事本編輯的中文,用sublime直接打開都不會亂碼,是因為它根據存儲的二進位數可以猜出要用GBK來打開。但是有的時候還是不行,用記事本編輯的什麼,在sublime中打開就會亂碼,因為它識別不出來是GBK,使用了UTF-8來解碼,這時就要人為去選擇reload with encoding中的GBK,就可以正常顯示了。

2.在使用過程中發現一個奇怪的現象:比如打開一個中文文件,默認正常顯示,這是選擇reload with encoding中的BIG5CP932等都會出現亂碼,這是正常的,而選擇UTF-8沒反應,正常不也應該亂碼嗎?此時如果用reopen with encoding中的所有選項都沒反應,然而事實上都應該亂碼才對。

摸索了很久發現一個規律,就是如果打開時默認使用的是reload with encoding中的編碼,想切換成UTF-8或者reopen with encoding中的編碼,需要這樣做

  • 先點擊reload中的UTF-8,再去點reopen中的UTF-8才是用UTF-8來解碼
  • 也就是說當前使用reload時想要切換到reopen需要先點reload中的UTF-8
  • 而如果當前使用reopen的編碼時,可以隨意點reopen和reload中的編碼都可以
  • reload中的UTF-8無論何時都不起作用,它只是reload到reopen的一座橋樑

知道這點之後我們就可以自由地對一些文本使用各種各樣的編碼方式進行解碼了,會發現即使是亂碼很多種編碼的結果都是一樣的,說明這些是相似的編碼方式。

3.如何改變文件編碼。比如用記事本保存默認用的GBK編碼,可以在sublime中打開將文件編碼方式轉變成UTF-8。sublime自帶的選項是save with encoding,這裡也不支持將文件轉變成GBK進行保存,於是ConvertToUTF8這個插件又增加了一個選項set file encoding to可以保存為GBK,這個設置完要再點一下保存。

這裡要說明一點,比如當前顯示中文字元,用的是GBK,你想換成UTF-8編碼,它的內部原理是這樣的

  • 當前存儲時使用的是GBK二進位編碼,每個字元對應兩個位元組,計算機只能對這些位元組進行操作
  • 點擊轉換成UTF-8的過程是:識別GBK中的位元組(二進位數),找到其對應的字元,再找到字元對應的UTF-8二進位位元組,用新位元組代替舊位元組
  • 如果這個文件實際上是用GBK編的碼會正常顯示中文,而你用BIG5去打開文件(解碼)得到一堆亂碼,此時再save轉化為UTF-8,那麼它去查詢和替換的會是亂碼字元的UTF-8二進位數,所以相當於你保存下了這些亂碼而把真正的東西丟掉了(雖然也可以往回推導找到原始文本)

如何知道一個文件的編碼

對於一個未知的文件,我們不能準確知道它的正確解碼方式,畢竟軟體也都是靠猜,因為很多種解碼方式都可以得到一個結果,有些結果在我們眼中叫做亂碼,但在計算機眼中都是一樣的,所以它無法辨別人眼到底能識別的是哪種結果。

我們能知道的只有

  • 當前軟體使用了什麼方式進行解碼
  • 這種方式解碼結果是不是我們想要的
  • 這個文件最有可能是哪幾種編碼方式,去試

首先我們可以看出當前軟體使用什麼方式進行的解碼

  • 在記事本中,點擊另存為,此時默認是什麼編碼就說明它是使用的這個方法解的碼
  • 在sublime中進行這樣的配置,在下方顯示解碼方式,如圖

其中左邊一個,右邊一個。左邊對應的是插件里的編碼,右邊對應的是sublime自帶的。看你打開文件時使用了什麼編碼,如果是用GBK,那麼右邊顯示的編碼就完全沒用不用看,如果是用reopen中的編碼,則左邊的不用看。

比如一個中文文件,我們使用UTF-8解碼,發現正常顯示中文了,說明我們的解碼方式正確,萬事大吉。如果亂碼了,說明文件不是用UTF-8編碼的,這時是無法得知確切編碼的,只能猜。這個文件是一個中國同學發給我的,他可能用的是GBK,於是去試GBK。不行的話可能再想這個文件最初是一個日本人創建的輾轉到我這裡來,於是再試日本的編碼。如果都不行就沒轍了,或者一個個試下來。

R文件亂碼問題

在weindows下,R語言中無論是R還是rstudio,如果有中文注釋都是使用GBK進行保存。但是最好將打開和保存文件設置為默認UTF-8,這在rstudio中是可以設置的。R裡面應該是不能設置的。

如果要和別人一起完成工作,他的電腦是mac,發給你的R文件都是utf-8編碼的,你打開的話中文(比如注釋)就會亂碼,所以最好設置默認UTF-8。但是我們在rstudio設置之後,如果去打開以前的R文件,其中的中文也會亂碼;那些沒有設置過的windows小夥伴發來的.R文件也會出現中文亂碼,但是這不是不換UTF-8的理由。如果你要和mac系統的人合作,同時要和日本人合作,和韓國人合作,他們默認保存的是UTF-8、日本設計的編碼、韓國設計的編碼,而你使用的是中國設計的編碼GBK,遇到編碼不兼容問題,最後商討的結果一定是大家都換UTF-8來統一,所以早換晚換都要換,兩個人編碼不一樣一定是非UTF-8的妥協。早一點換會讓你電腦中GBK的文件少一些,之後修改編碼方便一些。

在rstudio中修改默認編碼的方式:tool-global options-code-saving-change選擇UTF-8

設置完我們用R和rstudio進行試驗

在R中編輯腳本保存後用rstudio打開,如下圖

在rstudio中選擇reopen with encoding,選擇GB2312就可以正常顯示中文了。如果用R打開rstudio的UTF-8應該是沒有方法進行轉化的,所以還是全用rstudio就好。

在rstudio中也可以選擇save with encoding改變文件的編碼方式。

python腳本開頭提示

我們經常會看到一些python腳本文件(.py文件)最開始兩行總是這樣的

#!/usr/bin/env python3n# -*- coding: utf-8 -*-n

其中第一行是在Linux/OS X中聲明這是一個可執行的python程序,在windows下無用,可以忽略。

第二行是告訴Python解釋器,按照UTF-8編碼讀取腳本代碼

但是在python3中其實第二條也沒有必要了,參考這個回答。

專欄信息

專欄主頁:Data Analysis

專欄目錄:目錄

版本說明:軟體及包版本說明

推薦閱讀:

【科普】全球首個支持簡體中文的編碼
為什麼要有UTF-8的疑問?
tomcat伺服器8.0版本對於request發出的請求是否默認按照utf-8編碼而不是之前的iso8859-1?
能否詳細介紹一下字元編碼的發展歷史?
」手機複製這幾個字看你多久能刪完」這句話有什麼秘密能讓人刪不完?

TAG:字符编码 | 乱码 | Python |