前言 :
最近在處理文件時發現了同樣類型的文件使用的編碼可能是不同的。所以想將文件的格式統一一下(因為UTF-8的通用性,決定往UTF-8統一),遇見的第一個問題是:如何查看現有文件的編碼方式。 上網找了一下,找到幾篇比較好文章,這裡就不轉載啦把鏈接搞過來。
文件編碼問題集錦
字符串編碼(charset,encoding,decoding)問題原理
Java編碼淺析
判定文件編碼或文本流編碼的方法
問題描述 :
上面的幾篇文章可以看成認識編碼問題的“從入門到精通”
如果你看完了上面的文章,一定了解到了,在java中,class文件採用utf8的編碼方式,JVM運行時採用utf16。 Java的字符串是永遠都是unicode的,採用的是UTF-16的編碼方式。
想測試一下,java對UTF-8文件的讀寫的能力,結果發現了一個很鬱悶的問題,如果通過java寫的UTF-8文件,使用Java可以正確的讀,但是如果用記事本將相同的內容使用UTF-8格式保存,則在使用程序讀取是會從文件中多讀出一個不可見字符。
測試代碼如下:
* 類別 UTF8Reader 代碼 :
- package test;
- import java.io.*;
- public class UTF8Reader {
- public static void main(String argsp[]) {
- String path = "E:\\EclipseProjects\\FlibTest\\Data\\utf8.txt";
- File f = new File(path);
- try{
- FileInputStream fis = new FileInputStream(f);
- BufferedReader br = new BufferedReader(new InputStreamReader(fis,"UTF-8"));
- String line;
- while((line=br.readLine())!=null) {
- byte[] lineBinary = line.getBytes("UTF-8");
- System.out.println(line);
- for(int i=0;i
- int tmp = lineBinary[i];
- String hexString = Integer.toHexString(tmp);
- // 1個byte變成16進制的,只需要2位就可以表示了,取後面兩位,去掉前面的符號填充
- hexString = hexString.substring(hexString.length() -2);
- System.out.print(hexString.toUpperCase());
- System.out.print(" ");
- }
- System.out.println();
- }
- br.close();
- fis.close();
- }catch(IOException e){
- e.printStackTrace();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
正常的測試結果應該是直接輸出utf.txt的文本內容。可是實際上卻輸出了下面的內容:
第一行多出了一個問號。
通過上面的幾篇文章應該可以想到是Java讀取BOM(Byte Order Mark)的問題,在使用UTF-8時,可以在文件的開始使用3個字節的"EF BB BF"來標識文件使用了UTF-8的編碼,當然也可以不用這個3個字節。
上面的問題應該就是因為對開頭3個字節的讀取導致的。開始不太相信這個是JDK的Bug,後來在多次試驗後,問題依然存在,就又狗狗了一下,果然找到一個如下的Bug:
Bug ID:4508058
不過在我關掉的一些頁面中記得有篇文件說這個bug只在jdk1.5及之前的版本才有,說是1.6已經解決了,從目前來看1.6只是解決了讀取帶有BOM文件失敗的問題,還是不能區別處理有BOM和無BOM的UTF-8編碼的文件,從Bug ID:4508058裡的描述可以看出,這個問題將作為一個不會修改的問題關閉,對於BOM編碼的識別將由應用程序自己來處理,原因可從另處一個bug處查看到,因為Unicode對於BOM的編碼的規定可能發生變化。也就是說對於一個UTF-8的文件,應用程序需要知道這個文件有沒有寫BOM,然後自己決定處理BOM的方式。
紅色部分的"EF BB BF"剛好是UTF-8文件的BOM編碼,可以看出Java在讀文件時沒能正確處理UTF-8文件的BOM編碼,將前3個字節當作文本內容來處理了。
使用鏈接中提供的代碼可以解決碰到的亂碼問題:
http://koti.mbnet.fi/akini/java/unicodereader/
解決方案 :
這裡採用的方法是撰寫一個 UnicodeReader 類別繼承 Reader 來確認前方的 BOM. 首先會讀四個 Bytes, 接著判斷該四個 Bytes 是否為已知的 BOM, 如果是的話則根據不同編碼的 BOM, 退回多的的 Byte, 並以 BOM 後的第一個 Byte 當作一般我們讀取 Stream 的開頭. 否則將之前讀入的四個 Bytes 原封不動的退回, 並從頭開始處理這個 Stream. 該類別實作如下 :
透過剛剛介紹的 UnicodeReader 與修改測試代碼中的輸入流後 :
* 類別 UTF8Reader2 代碼 :
- package test;
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- public class UTF8Reader2 {
- public static void main(String args[]) {
- String path = "E:\\EclipseProjects\\FlibTest\\Data\\utf8.txt";
- File f = new File(path);
- try{
- FileInputStream fis = new FileInputStream(f);
- BufferedReader br = new BufferedReader(new UnicodeReader(fis,"UTF-8"));
- String line;
- while((line=br.readLine())!=null) {
- byte[] lineBinary = line.getBytes("UTF-8");
- System.out.println(line);
- for(int i=0;i
- int tmp = lineBinary[i];
- String hexString = Integer.toHexString(tmp);
- // 1個byte變成16進制的,只需要2位就可以表示了,取後面兩位,去掉前面的符號填充
- hexString = hexString.substring(hexString.length() -2);
- System.out.print(hexString.toUpperCase());
- System.out.print(" ");
- }
- System.out.println();
- }
- br.close();
- fis.close();
- }catch(IOException e){
- e.printStackTrace();
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
執行,可以看到正確的結果。
沒有留言:
張貼留言