我遇到的字符編碼的那些坑

我遇到的字符編碼的那些坑

 

如圖創建utf8.c源文件並以utf-8編碼保存

 

編譯運行後把文件拷到windows用UE打開並查看其16進制值

 

查看哈字的UTF-8 16進制編碼值,可以看到,utf8.dat,中的哈字是以UTF-8編碼保存的,佔用了3個字節

 

如圖創建gb18030.c源文件並以gb18030編碼保存

 

同樣的套路

可以發現gbk18030.dat 中哈字是以gbk編碼保存的,gbk編碼只用了2個字節

 

我們可以簡單粗爆的得出一個可能是錯誤但又可能是事實的一個結論,c語言中源文件中的字符串常量是以源文件的字符編碼編碼的(python,java也是的但是會解碼成unicode再放到字節碼中)這個結論可能看起來有點顯而易見,但如果沒思考過,沒測試過,我還真tn不知道啊

 

測試代碼中是直接使用linux系統調用來寫文件的,沒有標準IO庫的,二進制模式打開,文本模式打開文件之分,無論那種方式實質都是調用write寫的一個個字節。區別是你寫一個int類型的1,要4字節,用文本編輯器打開,無法查看,需要去解析。一個char類型的‘1’,要1字節,只要你有正確的編碼,最終,文本編輯器還是能識別出正確的字符編碼並展示出來

 

在C語言中,你的源代碼保存的什麼字符編碼,那麼源代碼中的字符串常量就是什麼字符編碼,你向文件寫什麼就是什麼,你從文件讀什麼就是什麼,,但在Python,java中讀寫文本就不同了 ,它經常會揹着你搞些小動作,也不是背啦,如果你知道就是明目漲膽,只能說它們的函數封裝的都太歷害了,,,

 

以前搞c語言時,如果只在本機測試玩玩,那有什麼字符編碼問題啊,在我第一次用Python時遇到字符編碼的問題時,我這樣想

 

問題1:

我遇到的第一個問題當然是這樣,在Python 2中我在py文件中寫進了中文字符它就會報如下錯誤SyntaxError: Non-ASCII character '\xb9' in file gbk.py on line 4, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

然後我們會查下資料,在源文件中開頭加入#coding:gbk 或#coding:utf-8告訴解釋器我這個是xxx編碼的,你用這個xxx字符編碼去把字符串解碼成unicode,這個xxx編碼要設定成py文件保存時的字符編碼

 

問題2

我遇到的第二個問題當然是這樣,在處理字符串時有時會進行字符串拼接,當我不小心,(其實有時候我根本不知道我正在使用的串是str串還是unicode串),拼接了一個unicode串與str串時就遇到了如下錯誤,

UnicodeDecodeError: 'ascii' codec can't decode byte 0xb9 in position 0: ordinal not in range(128)

當在Python2中str串與unicode串拼接時會把str串按defaultencoding去解碼成unicode串,而在python2中defaultencoding是ascii,要避免出現這種解碼錯誤我們要在與unicode串拼接時先把str串按正確的編碼解碼成unicode串,或把unicode串編碼成str串再拼接,或者使用sys.setdefaultencoding,把默認字符編碼設成你處理的str的編碼,就可以隨便拼接字符串了

 

問題3

我遇到的第三個問題當然是這樣,在Python2中用open打開文件,read讀出來的就是str,write寫str寫進去的也是str,這點和C語言倒一樣,但如果你要寫unicode串,那麼報歉不行,它會把unicode串編碼成defaultencoding,再寫進去。這時如果你寫的有中文,如果你沒把defaultencoding設成你想要輸出文件的字符編碼,就又會出現這個異常

UnicodeEncodeError: 'ascii' codec can't encode character u'\u54c8' in position 0: ordinal not in range(128)

 

 

問題4

我遇到的第四個問題當然是這樣,在,Python2中你用code.open以指定的字符編碼打開文件,read讀出來的就是解碼後的unicode,write unicode時會把unicode編碼成打開文件時指定的編碼,write str時可能又會遇到問題2,此時str會以defa ultencoding被解碼成unicode,再把unicode編碼成打開文件時指定的編碼。這時如果你寫的有中文,如果你沒把defaultencoding設成你想要輸出文件的字符編碼,就會又出現問題2這個異常UnicodeDecodeError: 'ascii' codec can't decode byte 0xb9 in position 0: ordinal not in range(128)

 

這幾個問題對於一個python新手來說,時常讓人摸不到頭腦,就字符串操作這點java比python好多了,至少java不會揹着你搞太多解碼編碼的的操作,你要搞,你就光明正大,堂堂正正的搞啊

 

 

問題5

我遇到的第五個問題當然是這樣

Windows BOM頭的坑

用java在windows寫了一個小工具,這個小工具,會讀取一個配置,把配置解析成String的key ,value放到map中,測試中怎麼搞都有這樣一個問題,有一個key找不到,但遍歷map又能遍歷到這個key,value,開始以爲是java的HashMap有問題,測啊測,最後把key一個字節一個字節的print出來發現第一個key多了3個字節,EF BB BF,用UE打開配置文件16進制查看,看到文件開頭也有這3個字節,百度一下這3個字節原來是BOM頭,用記事本打開另存爲無BOM UTF8搞定,哈哈。或者修改代碼,打開文件後先讀取3字節,如果是EFBBBF則丟棄,正常情況下這3個字節不會對程序造成影響,這3個字節是不會被展示出來的,我們用文本編輯器是看不到這3個字節的,但是一旦涉及到字符串比較就會有問題,,,

 

 

問題6

我遇到的第六個問題當然是這樣,

我們使用過一個叫Ambari的東西,這個玩意中有很多腳本是用Python2寫的,即然是用Python2寫的怎麼會沒有字符編碼的問題?

那天我們在文德路6號大院,下班了,我看小F還沒有想走的意思,我就跑過去

我:老鐵還不走?

F:你不是搞過Python?來看看我這是怎麼了。

我:略懂,略懂,,,

哎喲,字符編碼的問題,我想,TMD。

嗯嗯,看看日誌,系統重啓,加載配置時出問題了,哈哈,解碼異常,

你做了什麼操作?

F:改了下配置,重啓了下Ambari

我:哦,這樣啊,(這句臺詞是跟我大H哥學的,每當我不想理別人的時候,或聽不懂別人在說什麼的時候,我就會說,這樣啊),

兩個人79=86  37=21,瞎j8亂搞一通,這樣,這樣,不對,是那樣那樣,最後發現通過Ambari管理界面,修改完配置後,配置會在Ambari管理節點生成而後上傳到被管理集羣的各個節點上去(是這樣?記不清了)

問題來了,一個文件從一個服務器跑到另一個服務器,基於我遇到的以上四種Python編碼問題,我當時就覺得,服務器間操作系統的字符編碼可能不一樣

我:你看下管理節點也就是生成配置文件的服務器與啓動失敗報錯的服務器的操作系統的字符編碼是不是一樣的

F:不是一樣的

我:哦,那你把管理節點服務器操作系統的字符編碼改成啓動失敗報錯的服務器的操作系統的字符編碼,重新改下配置,當他在改字符編碼期間,我開始洋洋得意的開始猜測,肯定是這樣紙的啦,有人在Ambari服務啓動後修改了管理節點服務器操作系統的字符編碼,所以修改了配置後配置文件的字符編碼就變了,當你再次重啓Ambari服務並重啓整個集羣時,就出現了字符編碼問題

F:改好了集羣還是啓不來呀,還報這個錯

我:什馬,這樣啊,那你繼續加班吧,弟弟我先退下了

哈,哈,哈,哈,啪,啪,啪,分分鐘打臉

第二天

我:老鐵,那個問題解決了?

F:什麼問題?

我:就是那個字符編碼的問題

F:好了

我:什馬?怎麼搞的

F:重啓了下Ambari服務

我:哦,這樣啊,原來是這樣啊,我開啓事後小諸葛模式,已經啓動的進程,在啓動時已經把,操作系統的環境變量拷貝到,自己的進程空間了,我們修改完操作系統的環境變量後,是需要重啓進程才能獲取最新的環境變量,哎,F這點常識你都不懂,搞什麼啊。

 

 

問題7

我遇到的第七個問題當然是這樣,

一天維護的同事電我,說什麼,分隔符中的$符號被吃了,入庫失敗,文件大量積壓,我一看,你,你,你去找協議解析的,肯定是它們那邊數據有問題,給了我們亂碼的數據,導致文件亂碼,我的程序沒問題,我們的程序怎麼會有問題?我的程序什麼都沒做呀,對這個字段,我的程序怎麼會把這個$符號吃掉?

 

事情是這樣的

我們有個C++程序,這個程序負責把一個結構體中的各個字段取出來,格式化成一個以$[sb]爲分隔符的C字符串,然後把這個字符串寫到kafka。這個結構體中有一個字段是一個char數組,裏面存放的是以gb18030編碼的中文字符串,所以最後拼出來的這個字符串就是一個以gb18030編碼的字符串。。。???

因爲gbk編碼是兼容ascii編碼的,所以我們敢號稱我們寫進kafka的這條數據是以gb18030編碼的字符串,或叫它字節數組。

然後,我們有很多Java程序會從kafka中拉這條數據,獲取到這條數據後它們都二話不說,new String(byte[],”gb18030”) 去解碼,生成一個unicode編碼的String對象,然後以$[sb]爲分隔符分隔字符串,然後進行相應業務處理。這沒問題啊,這。這是沒問題,但是,當這條數據不是以gb18030編碼的字符串或者說C語言結構體中那個char數組中保存的不是以gb18030編碼的字符串,它就是一坨二進制的東西,會出現什麼問題?一般情況也不會出問題,但小概率事件,總還是會發生的。由於這個字符串本身不是以gb18030編碼的,所以當解碼成unicode時,char數組中的最後一個字節就與分隔符中的$做爲一個gbk字符編碼組成兩個字節去轉換成unicode字符編碼了,

 

爲啥唯獨char數組中的最後一個字節與分隔符中的$符號會被當成一個2字節的gbk編碼?

 

再囉嗦一下哈,因爲gbk與ascii 是兼容的,從char數組中的第一個字節,直致char數組的到數第二個字節,都能以一字節,或兩字節組合轉換成unicode,又剛好char數組中的最後一個字節大於127,所以解碼時會再取一個字節,把這兩個字節當作一個gbk編碼,去轉換成unicode,,接下來的字節又都是小於127的ascii字符了,所以,最後這條數據少了一個分隔符導致數據錯位了

 

這種問題怎麼處理?

 

這種問題在c,python,中很好辦,處理它不必把它轉換成unicode再去處理(python是個神奇的語言,無論是unicode串,還是gbk串你都可以‘’.split(‘$[sb]’)去分隔它,所以有時候你根本不知道你正在操作的字符串是以什麼編碼的,接下來又會變成什麼),因爲字節數組不會被重新解析組合生成新的unicode字節數組,所以分隔符不會被吃掉。C++程序格式化出的這個字符串,處理時只要不去轉碼,以ascii碼的方式去處理它,直接按$[sb]還是可以正確分隔字符串的,除非你運氣差到了極點,剛好這個char數組中有四個字節,剛好是$ [ s b ] 而它們又恰好在一起組成了我們的分隔符,如果運氣真被到了這個點,那麼我們還是可以轉義的。如果要轉碼只需轉char數組中的內容就可以了,然後再把各個字段以$[sb]爲分隔符拼到一起,你就可以對別人說我這是以XXX編碼的字符串了,在Java 中你可能要搞個分隔byte數組的函數了,,,,

 

問題8

這個問題來自一個已經運行了很多年的C++程序,當我第一次遇到這個問題時,我對別人說那你就不要在配置文件中配中文路徑啊

然後我第二次遇到這個問題,我又對別人說,

我:那你就不要在配置文件中配中文路徑啊,

他:這個文件夾我們無權修改,只能這樣,

我:嗯,嗯,那,那,好,好,我這邊看一下吧,

 

我就想爲啥英文路徑就沒問題?

然後我在配置文件中配入中文路徑,把配置文件保存成操作系統的字符編碼,試了下,唉,成功了,,,

在配置英文路徑時我從來沒關注過,操作系統是什麼字符編碼,配置文件是什麼字符編碼

我去查找gbk,gb2312,gb18030,utf8,ascii的相關資料,並測試。原來,gbk,utf8都是兼容ascii的,當在gbk編碼中一個字節大於127,纔會用2字節去表示一個gbk字符,在utf8中當字節的第一個bit是0時表示是ascii字符,當第一個bit爲1時,後面有幾個bit是1,就代表,接下來將要讀取幾個字節去表示一個字符,因此當配置文件中只有鍵盤上的那些字符時,無論它的字符編碼是什麼,都是小於127的一個字節一個字節的ascii字符

 

然後我就想爲在什麼配置文件配中文時會出現這個問題?

其實對於C語言我們從文本配置文件中讀一行數據,這一行到底是個什麼?

C字符串,對,就是一個以‘/0’結尾的char數組,你寫進去什麼,讀出來就是什麼,我們寫一個gbk編碼的有中文的字符串寫的是什麼?其實就是把char數組中的字節從用戶空間拷到內核空間文件系統的緩衝區再拷到磁盤,那麼讀出來的也是從磁盤拷到文件系統的緩衝區,再拷到用戶空間我們的char數組中,此間操作系統老老實實的,對於那串gbk編碼的字節數組一點操作都沒有,僅僅只是拷貝,拷貝。那爲何拿一個有中文的gbk編碼的字節數組當做參數,傳給一個linux 的系統調用,會出現找不到文件路徑的錯誤?然後我又把配置文件改成操作系統的字符編碼utf8,又完全沒問題了,,,?????

 

從Python的兩個關於字符編碼的接口先來看一下,python的sys模塊中有一個方法getfilesystemencoding()當你在python中用unicode的文件名做參數去調用如:open,write,listdir,walk,這種與操作文件相關的方法時,python會用這個編碼把unicode編碼成str串,再把這個str傳遞給操作系統的open,write等接口,但是如果你在代碼中用一個gbk編碼的中文串做文件名,在一個以utf8爲系統字符編碼的操作系統創建一個文件,那麼你在另一塊代碼中用一個utf8編碼的中文串做文件名去訪問這個文件,是找不到這個文件的,java中你基本上都是用一個unicode編碼的String去操作,當然不會有這種問題,在c語言中如果你喜歡,你可以隨時隨地輕而易舉的,從圖片,視頻中扣幾個字節,然後在結尾加個‘\0’,以此爲文件名去創建一個文件

 

像python,java這種以unicode做爲中間編碼去保存字節碼的語言都會在把unicode編碼的字符串編碼爲操作系統默認的字符編碼的字符串後再把這個字符串傳遞給操作系統的接口

 

另一個方法getdefaultencoding(),如果在python中打開文件寫文件,如果沒如果沒指定字符編碼的話,寫unicode到文件中就會保存爲這個字符編碼

在操作系統中編輯保存文件,會默認保存爲LANG設定的字符編碼

 

 

然後,然後

看下linux-0.96內核源碼,從open系統調用開始,ctrl+左鍵,一路走到文件系統,發現,從open到文件系統通過路徑查找到文件的inode,是把那個char數組拷貝到內核,然後用”/”把路徑分隔開,從根目錄開始一級一級搜索匹配,怎麼匹配的?一個字節一個字節對比

ext_match這個函數的,第2個參數name就是路徑中的一段,第3 個參數

是struct ext_dir_entry ,這個結構體用來表明這個name在當前路徑下對應的文件的inode是什麼

ext_match中的asm彙編代碼就是把第2個參數name與de中的name一個字節一個字節做對比,如果對比上就使用inode編號去磁盤上讀取這個inode,讀出來的inode是個哈玩意?

 

磁盤上的每一個文件對應一個inode,用來描述文件存在何處,及什麼時候創建,文件類型等信息,inode是格式化磁盤時分配的,可以通過df -i 查看磁盤的inode使用情況,i_dev設備編號用於表明文件保存在那個磁盤設備上

 

 

我們平常stat 一個文件是這樣的

基本上stat的輸出linux-0.96的inode 中都是有的

Inode中的i_data存放的是文件存在磁盤上的那些block的編號,i_mode用來標識是目錄還是文件,如果是目錄就會從i_data取出block編號從磁盤讀出這個block(1kb,2個扇區),把這個從磁盤讀出block強轉成struct ext_dir_entry,如果i_mode是文件此block就是文件中的內容

 

 

如果有這樣一個場景,一個路徑要有三部分組成且都是中文,一部分來自網絡,一部分來自配置,一部分是在代碼中寫死的,那麼這樣拼接成的一個完整的路徑,如果各個部分的字符編碼都不一樣(事實是有極大的可能是不一樣),那麼你的程序將會報在找不到這個路徑的異常,事實上這個路徑是存在的,你還可以cd進去more一下文件,但程序就是報,No such file or directory,你百思不得其解吧

 

在Python2中模擬一個路徑由兩種字符編碼組成,py文件用gb18030編碼保存,系統默認字符編碼爲utf-8

 

可見只有路徑完全是utf-8編碼時才能找到路徑,雖然你看到的都是/哈/哈/,文件系統查找這個文件夾的inode也是通過你給的路徑從根目錄開始,一級一級,拿一個字節一個字節去對比的

 

‘/哈/哈/’ 路徑是一個gbk編碼組成的路徑 /\xb9\xfe/\xb9\xfe/,用這個gbk編碼組成的路徑,不能與文件系統中的目錄匹配上

 

'/哈/哈/'.decode('gbk').encode('utf8')是一個utf-8編碼組成的路徑,可以與文件系統中的目錄匹配上

 

'/哈/哈/'.decode('gbk')是一個unicode編碼組成的路徑,/\xe5\x93\x88/\xe5\x93\x88/只有用這個路徑去listdir纔不報錯,才能與文件系統中的目錄匹配上

 

'/哈'+'/哈'.decode('gbk').encode('utf8')這個路徑是一個gbk和utf-8兩種編碼組成的一個路徑其16進制值爲/\xb9\xfe/\xe5\x93\x88   b9 fe是哈的gbk編碼,e5 93 88 是哈的utf-8編碼

 

 

 

所以當你在配置文件中配了中文路徑,而此配置文件的字符編碼又與操作系統的字符編碼不一致,就會出現找不到文件的異常

而當只有ascii字符時,無論你的配置文件是什麼字符編碼,操作系統是什麼字符編碼,都不會出現這種問題,至少gbk,utf-8是兼容ascii碼的,utf-16是不兼容的

 

 

如果是字符串就一定會有個字符編碼吧,如果解碼,編碼老是出問題,我就懷疑這個字符串中混進了一坨二進制的東東,而這種問題很有可能會出現

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