在上一篇中,探討了文件名編碼以及非文本文件中的文本內容的編碼,在這裏,將介紹更爲重要的文本文件的編碼。程序員
設想一下,若是在保存文本文件時,也同時把所使用的編碼的信息也保存在文件內容裏,那麼,在再次讀取時,肯定所使用的編碼就容易多了。eclipse
不少的非文本文件好比圖片文件一般會在文件的頭部加上所謂的「magic number(魔法數字)」來做爲一種標識。所謂的「magic number」,其實它就是一個或幾個固定的字節構成的固定值,用於標識文件的種類(相似於簽名)。好比bmp文件一般會以「42 4D」兩字節開頭。工具
又好比Java的class文件,則是以四字節的「ca fe ba be」打頭。(咖啡寶貝?)字體
即使沒有文件後綴名,根據這些信息也是肯定一個文件類型的手段。大數據
附:關於用Notepad++查看十六進制的問題,這是一個插件,若是沒有裝,菜單--插件--plugin manager--available--HEX-Editor,裝上它。裝上後,它一般在工具欄的最右邊,一個黑色的大寫的斜體的「H」就是它。單擊它能夠在正常文本與16進制間切換。要進一步查看二進制,在文本區,右鍵--view in--to binary。編碼
那麼,對於文本文件,有沒有這樣的好事呢?能夠簡單創建一個文本文件「foo.txt」,裏面輸入兩個簡單的字符,好比「hi」,保存,而後再查看文件的大小屬性spa
而後,咱們很遺憾地發現,大小隻有2,也即「hi」兩個字符的大小,這意味着沒有保存額外的所用編碼的信息。操作系統
用十六進制形式查看,也能夠發現這兩個字節就是hi兩字符的編碼:.net
關於字母的ASCII編碼,可查看字符集與編碼(八)——ASCII和ISO-8859-1。插件
那麼,如今很清楚了,文本文件僅僅是內容的字節序列,沒有其它額外的信息。
固然,說絕對沒有額外信息也不徹底正確,在以前的關於BOM的介紹中,咱們看到BOM其實能夠當作是一種額外的信息。
保持內容不變,簡單地「另存爲」一下,在編碼一欄選擇「UTF-8」,再次查看屬性將會發現大小變成了5.
再次查看十六進制形式時,就會發現除了原來的「68 69」外,還多出了UTF-8的BOM:「ef bb bf」
也正是以上三個與內容無關的字節使得大小變成了5.
這個信息是與所用編碼有關的,不過它僅能肯定與Unicode相關的編碼。
嚴格地說,BOM的目的是用於肯定字節序的。
另外一方面,對於UTF-8而言,如今一般不建議使用BOM,由於UTF-8的字節序是固定的,因此不少的UTF-8編碼的文本文件實際上是沒有BOM的,不能簡單地認爲沒有BOM就不是UTF-8了。
好比eclipse中生成的UTF-8文件默認就是不帶BOM的。微軟的筆記本應該是比較特殊的狀況。
綜上所述,文本文件一般沒有一個特殊的頭部信息來供肯定所用的編碼,另外一方面,編碼的種類又是五花八門,那麼如何去肯定編碼呢?
不妨就以記事本爲考察對象,去探究一下它是如何肯定編碼的。
前面說了,BOM做爲一種額外的信息,間接地代表了所使用的編碼。儘管它本來的意圖是要指明字節序,但曲線救國一下也未必不可。何況記事本還主動地爲UTF-8也寫入了BOM,不加以利用這一信息天然是不明智的。
注:對UTF-16來講,BOM是必須的,由於它是存在字節序的,弄反了字節序一個編碼就會變成另外一個編碼了,那就完全亂套了。不過通常不多用UTF-16編碼來保存文件的,更可能是在內存中使用它做爲一種統一的編碼。
但對於UTF-8,不少時候也是沒有BOM的,記事本遇到UTF-8 without BOM時又該怎麼辦呢?
若是內容中沒有編碼信息,又要去肯定它使用的編碼,這不是爲難人是什麼?好在「坑蒙拐騙」中的第二招「蒙」能夠拿來用用。
「蒙」其實也是要講點技術含量的,簡單點天然就是是模式匹配了,或許一個或幾個正則式就完了;複雜點,什麼機率論,統計學,大數據通通給它弄上去,那逼格立馬就高了有木有?固然了,記事本也就是一跑龍套的...
在編碼界有這麼一個傳說:記事本跟「聯通」有仇。這是怎麼一回事呢?
新建一個文本文件「test.txt」,錄入兩個漢字「聯通」,保存,關閉程序而後再次打開這一文件:
咦,這是什麼鬼?我們的「聯通」呢?
這其實就是競猜失敗的結果了,準確地講,記事本把編碼給猜成了UTF-8.
爲何說是猜成UTF-8形成的呢?我也沒見過源碼!接下來會根據出現的現象,已有的證據來做出咱們的推論,而後還會作些實驗去驗證。(沒錯,這就是科學!)
首先是這樣一個事實:當咱們保存時,使用的是缺省編碼,也就是GBK。
「聯通」兩字的GBK編碼以下:
而後,是這樣一個現象:再次打開時,記事本忽然就翻臉不認人了,顯示出了一些奇怪的字符。
嚴格地講,有三個字符,兩個問號及一個C同樣的字符,後面會分析爲什麼會這樣。
以上字節咋一看也沒啥子特別的,領頭字節也沒有剛好等於UTF-16或UTF-8的BOM,絕對標準的GBK模式,爲啥記事本對它刮目相看了呢?
關於GBK等編碼,可參見字符集與編碼(九)——GB2312,GBK,GB18030
那麼,一個合理的猜想就是記事本可能把它當成了無BOM的UTF-8編碼。
而對於UTF-8編碼來講,它的編碼模式仍是頗有本身特點的,那麼咱們換成二進制形式查看以上編碼:
看到以上編碼,相信對UTF-8編碼模式有一些瞭解的都知道是怎麼回事了,我也用顏色的下標標註了關鍵的部分。
在前面的篇章中,也曾經幾回說到過UTF-8的編碼模式:
對比一下,不難發現上述兩個編碼徹底符合二字節模式!也難怪記事本犯迷糊了。
天然,要作出一些「科學」的發現,你仍是須要必定的基礎的。因此在這裏也給出了不少前面文章的連接。
那麼,爲什麼又顯示成了那樣的效果呢?
既然推斷它是UTF-8編碼,讓咱們人肉解碼一下:
前兩字節構成一組:11000001 10101010
有效的碼位有三段:11000001 10101010
重組成碼點以後是:00000000 01101010
以上碼點寫成16進制是U+006A,這明明是一個一字節的碼點!對應的字母實際上是小寫字母「j」。
因此,這裏實際上是有錯誤的。這個碼點不該該用二字節來編碼。
若是你讀過前面關於Unicode的篇章,就會明白,對於UTF-8編碼而言,碼點在U+0000~U+007F(0-127)間的用一字節模式編碼。碼點在U+0080~U+07FF(128-2047)間的才用二字節模式編碼。
別問爲何!這叫烏龜的屁股——龜腚(規定)。
理論上講,二字節的空間是徹底能夠囊括一字節編碼的那些碼點的,各類模式間實際上是有重疊與冗餘的。但若是一個碼點適用於更少字節,那麼它應該優先用更少字節的編碼模式。
因此,一般說UTF-8的二字節模式是「110x xxxx, 10xx xxxx」,但並不是全部知足這些模式的編碼都是合法的UTF-8二字節編碼。這裏面實際上是有個坑的。二字節首個碼點爲U+0080,對應二字節模式首字節爲「1100 0010」,那麼,全部合法的二字節模式首字節不該該小於此值。
顯然,親愛的微軟的寫記事本的程序員們,大家偷懶了!大家至少應該能夠避免與「聯通」結仇。
那麼,雖然可以解出相應的碼點,但實際上是非法的組合,這也就是結果顯示出「�」的緣由。這一般是一個明顯的解釋失敗的標誌。
注:「�」自己是一個合法的Unicode字符,碼點爲U+FFFD,對應的UTF-8編碼爲:EF BF BD。若是字體不支持的話,可參見http://www.fileformat.info/info/unicode/char/fffd/index.htm
這可不是「顯示不出來」形成的,它自己就長這樣。
這個碼點的含義爲「replacement character」(替換字符)。當碰到非法的字節時,顯示系統就用它來替換,而後顯示這個替換字符來表示發生了替換。因此,那些真正「顯示不出來」的東西已經被替換了。(若是文件中自己就包含這個字符的話,替換上去的和原有的實際是沒法區分的。)
在這裏,程序實際臨時在內存中作了替換,若是你對它進行拷貝,獲得的也將是它的值,而不是原來的值。
因此,顯示層面出現了問號(包括早期的ASCII中那個問號「?」,U+001A,也經常使用做替換字符),不表明它不清楚如何顯示,而徹底是由於最終交給它去渲染的就是「問號」字符。(多是替換上去的,也可能自己就有的。)
在顯示層面,原來的值實際已經丟失了。
至於爲什麼顯示了兩個問號,大概是把兩個字節看成了兩次失敗。我的認爲顯示一個問號也能說得通。
再來看第二組
後兩字節構成一組:11001101 10101000
有效的碼位有三段:11001101 10101000
重組成碼點以後是:00000011 01101000
以上碼點寫成16進制是U+0368,對應的字母實際上是所謂的COMBINING LATIN SMALL LETTER Cͨ(<--這裏第二個C就是U+0368)。見「http://www.fileformat.info/info/unicode/char/0368/index.htm」
顯示時,它會緊貼在前面的字母上,這是Unicode中我的感受比較奇葩的一些內容,若是你有興趣,這是wiki的一些介紹http://en.wikipedia.org/wiki/Combining_character
前面亂碼的截圖中,那個怪怪的C就是這樣來的,感受好像與前面一個問號是一體的同樣。
「聯通」這一案例還真多梗。
至此,冤有頭,債有主,一切都水落石出。最終咱們的猜想被證明。
其實,在變成怪怪的字符後,若是咱們點擊「另存爲」,在彈出的對話框中會發現編碼成了「UTF-8」
這是赤裸裸的證據,直接代表了記事本把編碼誤判成了UTF-8!簡直是鐵證如山呀!
刪掉test.txt,重新再創建,此次把聯通的好基友「電信」也一塊兒錄入,錄入「聯通電信」四個字,保存並再次打開時記事本就再也不抽瘋了,由於「電信」兩字的GBK編碼模式與UTF-8不能匹配了,讀者可自行驗證一下,這裏就再也不貼圖展現了。
注:不要直接刪除原來的亂碼字符並從新錄入,前面「另存爲」已經代表它已經成了UTF-8編碼,直接在原文件修改將致使以UTF-8編碼保存。因此應該刪除原文件。
有句話叫「無巧不成書」,記事本跟「聯通」有仇,這其實就是一個由於樣本太少而誤判的典型例子。
若是既沒有BOM,又沒法猜想出所使用的編碼,那是否就只能是兩眼一抹黑了呢?
還好,計算機世界還有件貼心的小棉襖叫「缺省」。
其實,當你保存任何一個文本文件時,指定一個編碼是必不可少的一個步驟。
與此相似,讀取一個文本文件時,或者說是好比Java中new一個Reader字符流時,又或者是string.getBytes時,你其實都是須要指定一個編碼的。
但不少時候,咱們並無感受到須要這一步驟,緣由就是「缺省」在爲咱們默默地服務。
缺省這玩意,怎麼說它好呢?當它正常時,你好,我好,你們好。當它不正常時,你甚至不知道哪兒出錯了。你過於依賴它,它極可能成爲你的定時炸彈。不得不說,不少時候咱們實際上是抱着炸彈在擊鼓傳花,還玩得不亦樂乎,直到「轟」的一聲,咦?頭上何時多了個圈?
以記事本爲例,當咱們新建一個文件並保存時,實際上是有個選項的,一般,這裏會缺省地選上「ANSI」
那麼這裏的ANSI又是個什麼鬼呢?
ANSI(American National Standards Institute美國國家標準學會)與它字面的意思並不相符,它也不是一種真正意義上的編碼。
一般把它理解成平臺缺省編碼,它具體指代什麼則一般與平臺所在地區的Windows發行版本有關。
像咱們這些火牆內的大陸人民,多數人用的Windows版本,ANSI指的是GBK;在香港臺灣地區,它多是Big5;在一些歐洲地區,它則多是ISO-8859-1。
除了ANSI以外,在這裏還有其它的選項。
其實在這裏短短的一個下拉列表,到處都是坑呀,說多了都是淚。
1. ANSI,前面已說,就不說了。
2. Unicode。實際上是UTF-16,具體地講是UTF-16 little endian(UTF-16 LE)。這個缺省爲Little Endian也僅是微軟平臺的缺省。其它平臺未必是如此。
3. Unicode big endian。與以前相似,就是UTF-16 big endian(UTF-16 BE)。Unicode如今的含義太寬泛,能夠指Unicode字符集,能夠指Unicode碼點,也能夠指整個Unicode標準。如今看來,把UTF-16繼續叫成Unicode實在是很坑爹,除了容易引起誤解,我還真沒想到它還能有什麼其它好處~
4. UTF-8.實際上是「帶BOM的UTF-8」,而真正推薦的缺省作法是「不帶BOM」。微軟就是任性!
還須要注意的是,不一樣的操做系統對於缺省有不一樣的策略。
好比如今不少的Linux的操做系統都把UTF-8當成了缺省的編碼,不管你在什麼地區都是如此。這對於減小混亂仍是有幫助的。
由於文件內容沒有編碼的信息,各個系統平臺對於缺省的規定又各不相同,種種狀況致使了亂碼問題層出不窮,下一篇,將探討引入編碼信息的一些實踐。