首頁 > 軟體

讓Vim徹底告別亂碼

2020-06-16 17:23:16

1 字元編碼基礎知識

字元編碼是計算機技術中最基本和最重要的知識之一。如果缺乏相關知識,請自行惡補之。這裡僅做最簡要的說明。

1.1 字元編碼概述

所謂的字元編碼,就是?對人類發明的每一個文字進行數位化表示。最經典的ASCII編碼就是西方人發明的針對英文字元的編碼方法,包括26個英文字母、數位、標點、特殊字元等。問題是,這種編碼的範圍是0-127,只能對128個字元進行編碼。當計算機來到其他國家後發現,除了英語,還有大量的其他語言,而且涵蓋的字元也遠遠多於128個。為此,各個國家開始針對自己的語言進行編碼工作,例如中國的GBK,日本的CJK,等等。

這雖然解決了ASCII編碼不夠用的問題,但是卻帶來了另外一個更加嚴重的問題。那就是各個國家的字元編碼不統一,導致無法進行統一處理。於是乎,著名的UNICODE出現了,UNICODE編碼範圍非常大,可以涵蓋全球所有語言的字元。

1.2 區分字元集(Charset)和字元編碼(Char Encoding)

這兩個術語有時候不進行區分的使用,但是理解其區別對於理解字元編碼至關重要。

  • 程式碼點(Code Point)
    也就是我們前面說到的,為每一個字元分配一個數位序號。例如在ASCII字元集中,字元A被分配成65號,那就是說A的程式碼點是65。一種編碼規範中,所有的程式碼點的集合就是字元集。

  • 字元編碼
    字元編碼是程式碼點的二進位制儲存格式。還是前面的例子,在ASCII字元集中,A的程式碼點是65。而這個65究竟是怎麼用二進位制0和1序列表示呢?這就是字元編碼的工作。在ASCII編碼中,這個65被儲存為01000001,一共佔據一個位元組(8個二進位制位)。

說到這裡也許你會覺得,這中區別也沒什麼啊,這主要是因為在我們的例子中ASCII字元集的程式碼點只有一種字元編碼方式,也就是ASCII字元編碼。而這在其他字元集中卻不總是這樣,例如UNICODE字元集。

UNICODE字元集,規定了全球每一個字元的程式碼點,例如英文字母A在UNICODE字元集中的程式碼點是65(哈哈,這個程式碼點與ASCII是相容的),然而65的存放格式卻有很多方式:例如在UTF-8字元編碼規範中被儲存為8個二進位制位:01000001,而在UCS-16中被儲存為16個二進位制位:0000000001000001,而在UCS-32中被儲存為32個二進位制位:00000000000000000000000001000001。

說到這裡,就明白了,UNICODE字元集對應有很多不同的字元編碼方式:UTF-8,UCS-16,UCS-32等等。
而ASCII字元集只有一種編碼方式:ASCII字元編碼。

UNICODE字元集的不同編碼方式是為了適應不同的環境而被創造出來的,例如UTF-8被用來網路傳輸,檔案存放,UCS-16則被用來作為記憶體中的存放方式,以利於快速統一計算。

現如今,雖然UNICODE字元集已經獲得廣泛採用,然而歷史遺留的其他字元集仍大量存在。
近年來,字元集的概念很少被提及,字元編碼則更多的被使用。

1.3 字元編碼與顯示

對字元進行編碼只是完成了存放、處理和傳輸,要想把字元的形狀繪製出來,還要有對應的字型以及渲染手段。

對於GUI程式,作業系統都會提供API來對指定字元進行渲染繪製。對於終端來說,終端有一個字元編碼的屬性,從而把接收到的二進位制位元組流按照這個字元編碼進行解析,然後呼叫相應的渲染引擎來對其進行顯示,詳情請參考我的一篇博文:從呼叫printf()到顯示器上看到字串。

2 VIM讀取、顯示、儲存文字檔案過程分析

2.1 VIM涉及到的字元編碼

(1) 磁碟檔案的字元編碼
存放在磁碟上的文字檔案,是按照一定的字元編碼進行儲存的,不同的檔案可能使用了不同的字元編碼。
這在VIM中被叫做:fileencoding。

(2) VIM緩衝區以及介面的字元編碼
VIM執行時,其選單、標籤、以及各個緩衝區統一使用一種字元編碼方式。
這在VIM中被叫做:encoding。

(3) 終端使用的字元編碼
終端同一時刻只能使用一種字元編碼,並按照這種編碼從接收到的位元組流中識別字元,並顯示,終端的字元編碼是可以動態調整的。
這在VIM中被叫做:termencoding。

2.2 vim讀、顯、存分析

(1)讀檔案
VIM開啟檔案時,並不知道檔案的字元編碼,所以不得不進行探測。探測是按照一定的優先順序進行測試。依據的標準就是:fileencodings。VIM逐一測試fileencodings變數指定的字元編碼方式,直到找到認為合適的然後把這種字元編碼方式設定為fileencoding變數。

然後把檔案中的編碼轉換成encoding指定的編碼方式,存入檔案緩衝區中。
(2)顯示檔案
vim把檔案讀取完畢並以encoding編碼存放到緩衝區記憶體之後,會根據termencoding指定的終端編碼方式,轉換成termencoding編碼後,寫入到終端。此時,終端按照自身的編碼屬性識別出一個個的字元,呼叫渲染引擎繪製到螢幕上。

(3)儲存檔案
VIM把緩衝區中的encoding編碼的位元組集合轉換成fileencoding編碼後寫入磁碟,完成檔案儲存。

可以看出,VIM涉及到的3種字元編碼之間的轉換:
讀:fileencoding—–> encoding
顯:encoding ——> termencoding
寫:encoding ——-> fileencoding

只要這三種轉換都不會出現問題,那麼VIM就可以正常工作,不會出現亂碼。
然而,並不是所有的字元編碼之間都能夠無失真轉換,例如GBK字元編碼轉換為ASCII編碼時,由於ASCII並不能完全包含GBK的字元,所以會出現問題。

3 常見亂碼情況分析

3.1 讀檔案時,VIM探測fileencoding不準確

這很好理解,比如以GBK編碼方式儲存的檔案,VIM把fileencoding探測成了ASCII,則肯定會出現問題。

【解決方法】一是靠VIM自身提高探測水平;二是設定合適的fileencodings變數,把最可能用到的編碼方式放到最前面。如果VIM實在是探測不對,那麼就只能通過 :set fileencoding=xxx 命令來手動探測了。

3.2 fileencoding編碼無法正確轉換到encoding編碼

例如,檔案採用GBK編碼,而ecoding使用ASCII,這樣大量的漢字字元無法被轉換,從而導致亂碼。
【解決方法】把encoding設定成UTF-8,目前為止UTF-8能包含所有字元,所以其他的任何編碼方式都可以無失真的轉換為UTF-8。

3.3 encoding無法正確轉換到termencoding

這個問題,與3.2類似。
【解決辦法】把termencoding設定為何encoding相同。預設termencoding=”“的情況下,這兩者就是相同的。

3.3 termencoding與實際的終端字元編碼不一致

例如本來字元終端的編碼屬性為GBK,而termencoding卻為UTF-8,那麼VIM就會錯誤的認為終端就是UTF-8編碼的,導致向終端輸出UTF-8編碼的位元組流,而終端卻按照GBK來識別,當然就會識別成亂碼。
【解決辦法】把終端實際的編碼方式和VIM的termencoding統一起來。

3.4 終端顯示能力欠缺

例如,傳統的字元終端,本身不具備顯示漢字的能力,雖然它可以識別出UTF-8編碼的漢字,但是渲染引擎無法正確繪製,也就顯示成了亂碼。
【解決辦法】盡量還是使用Putty等偽終端軟體,避免直接使用字元終端裝置;如果實在不能避免,就要避免使用ASCII字元集以外的字元,好好學習英文吧。

4 杜絕亂碼的最佳實踐

所有編碼統統設定為utf-8。這樣既能夠識別人類所有語言,又避免了各種編碼之間轉換的效能損失。

4.1 VIM設定

set encoding=utf-8
set termencoding=utf-8
set fileencodings=utf-8,gbk,latin1

如果無特殊要求和限制,磁碟檔案也以UTF-8方式儲存。

set fileencoding=utf-8

4.2 終端設定

常用的幾種終端軟體設定。
(1)Putty

(2)Mac Terminal

本文永久更新連結地址http://www.linuxidc.com/Linux/2017-01/139723.htm


IT145.com E-mail:sddin#qq.com