(原)一次揪心的亂碼排查過程


序)很多時候其實問題很簡單,問題在於自己懂得過於膚淺

  項目中需要用到一個功能,機器人模擬和人類聊天,玩家說出一句話之后,機器人本能的和他開始聊天,這破B玩意兒我覺得只要有強大的詞庫和拆分算法,就那么點東西,但是要自己做還真是壓力滿滿的。於是果斷的在網上搜索,輕松的找到了這個東西:

  

 

  這玩意兒給我的第一感覺就是實在,可以,完全能夠滿足需求,不過貌似它沒有提供接口,這不是事兒,果斷的翻網頁源碼,找到post的地方,是一個ajax的post請求,帶了一個參數,很簡單,於是果斷的來個java模擬HTTP,分分鍾搞定,寫了個junit測試下:

  

 

  10分鍾就搞定了一個智能聊天機器人,本來以為問題就解決了,於是輕松的部署到jetty去,鄙人對jetty緋聞挺多了,websocket中的EOF曾經就搞了很久,部署上去之后,果斷開始使用安卓端測試下,客戶端發了一個:你好,另一個客戶端收到:騫茶帿瀛�

  明顯是亂碼了,感覺debug一下:

  

 

  可以看到接受到http的返回值的時候已經亂碼了,在此過程中經歷過如下步驟:

  發出http請求 ---> 第三方接口http返回值 --> 本地jetty容器接受二進制流 --> jetty根據容器編碼重新編碼二進制數據 --> Java InputStream接受jetty編碼之后的流 --> java將其轉換成String

 

  於是我們看到了一個String的返回值,而這個返回值亂碼,一般在漢字亂碼中分為兩種情況:

  1:騫茶帿瀛�  這樣的亂碼其實不叫亂碼,而是數據不是我們想要的,因為我們要的是A卻顯示成了B,這樣的原因主要是因為編碼格式不正確導致

  2:?????  全是問號的亂碼應該很多人都遇見過,這樣的東西應該才是算亂碼,為什么會出現?。因為字節內的東西無法用一個漢字展示出來,也就是找不到漢字對應這個內容,於是這樣的東西會以?的形式展示出來,官方的稱呼就是編碼黑洞,對應的二進制數據為63,轉換后就是一個?

 

  根據情況來看自己遇到的是第一種,於是有點疑惑,管他的,來個強轉:

  

   ChangeCharset changeCharset = new ChangeCharset();
        try {
            result = changeCharset.toUTF_8(URLDecoder.decode(result, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;

  管你什么編碼,哥給你來個統一,於是美滋滋的再次打開客戶端測試,又出現了另外一種情況:

 

  

機器人說:你好愛你哦親??

  媽了個巴子,居然有部分亂碼,於是繼續測試想找出規律,后來果然發現規律,只要過來的數據是偶數個,則不會亂碼,若是奇數個,則最后一個漢字亂碼,亂碼的形式是固定的?,來了一個?,我靠,今天兩種情況都遇到了,本以為很簡單的東西居然卡在了編碼的地方,苦思冥想,很明顯是容器編碼問題,很SB的解決方法也很簡單,判斷下是不是奇偶,不是偶數加個東西就行了。

 

  但是想搞明白為什么是最后一個字亂碼,突然想到一個東西,UTF-8中,一個漢字3個字節,GBK中一個漢字2個字節,我好像明白了什么。。

 

  因為jetty容器默認是按照系統編碼來決定容器編碼,前提是沒有自己修改啟動編碼,而公司里我台PC是windows的,好像默認GBK的,反正我對windows緋聞也挺多的,於是這里有一個問題,比如jetty接受到了一串經過UTF-8編碼的漢字:

  我很好

  jetty收到的最原始的二進制數組是這樣的:

  [-26, -120, -111, -27, -66, -120, -27, -91, -67]

  當然這不是最原始的,最原始的0和1,當然為了好看就算他是最原始的吧,下一步jetty要開始編碼了,按照jetty的GBK編碼,他按照2個字節一個漢字的格式去編碼,於是出現了這樣的組合:

  [-26, -120]  [ -111, -27]  [-66, -120]  [-27, -91]  [-67]

  前面每兩個字節都能找到對應的漢字,最后jetty發現最后居然只有一個字節,找不到對應的漢字,心里想這SB是哪來的,於是jetty放棄它了,把它趕出去,把63丟過去,於是最后的組合成了:

  [-26, -120]  [ -111, -27]  [-66, -120]  [-27, -91]  [63]

  經過GBK的格式編碼,兩個字節對應一個漢字,就顯示出了這樣的東西:

  騫茶帿瀛

  會出現5個,因為每2個字節代表一個漢字,最后一個字節是63,對應的符號是?,就出現了上面的東西,於是我對它做了強制的UTF-8編碼,導致上面的二進制數組重新組合,經過UTF-8的組合之后,二進制數組成了這樣:

  [-26, -120, -111] [-27, -66, -120] [-27, -91, 63]

  再經過UTF-8顯示之后,變成了這樣:

  我很�?

  前6個字節能夠正常的顯示出漢字,因為那就是真正的數據,然而最后3個字節,已經被GBK處理了,替換過了,即使使用UTF-8也無法還原它原來的容貌,於是它就顯示成了上面的樣子,但是為什么偶數不會出錯?

  因為偶數能夠被GBK正常的解碼,也就是如果漢字是偶數,UTF-8和GBK是等同的,但是如果是奇數,則就出問題了,這也是傳說中的最后一個漢字亂碼的問題,因為最后一個 字節始終是63,要解決這個問題,必須要治標還要治本,項目中必須全程保證編碼一致性,因為我這個項目是游戲服務器,走的WebSocket,要是Servlet可以直接在Servlet里面處理或者Filter處理。

  不同的編碼方式,處理邏輯不一樣,很多時候我們強轉看似解決了問題,其實只是問題沒有暴漏出來,知道其根本,方能運籌帷幄之中,決勝千里之外。

 

  扯點題外話,前幾天半夜不知道抽了什么風,將Centos升級到了6.6,結果在6.6下eclipse全部打不開,全是fatal級別的錯誤,反正不是我等閑人能解決的,那一次更新下載的更新包是1.4GB,明顯是系統兼容性的問題,我了個擦,於是想到一代碼農不能死於eclipse下,於是折騰下了VIM,弄了幾個插件。雖然以前也用VIM,但是沒有這次這么正式,弄了下發現其實這玩意兒相當優秀,基本除了eclipse的打斷點Debug,其他和他差不多,寫C/C++更快,一個\im過去整個文件注釋main整體就起來了,相當便捷,另外Linux下的UI也相當不錯,特別是DUCK桌面,和Mac基本差不多,甚至你可以用java寫出這樣的東西:

  

  對於程序猿,Linux是個好東西,有喜歡試水的朋友,可以試試。

  附上最近給自己系統加了DUCK美容后的美照:

  

  

 

結語)其實這個問題很簡單,只是當時太SB,想想就揪心。。

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM