歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 讓Vim徹底告別亂碼

讓Vim徹底告別亂碼

日期:2017/2/28 13:44:28   编辑:Linux教程

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

Copyright © Linux教程網 All Rights Reserved