Java讀取UTF-8格式txt文件第一行出現亂碼——問號“?”及解決;Java讀帶有BOM的UTF-8文件亂碼原因及解決方法

 Java讀取UTF-8的txt文件第一行出現亂碼“?”及解決


test.txt文件內容:
A中
2國
3
4
5
6

 

test.txt文件採用寫字板保存爲UTF-8格式
保存並關閉後使用寫字板再次打開該UTF-8文檔,中文、字母正常顯示

 

測試代碼:

  1. import java.io.BufferedReader;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.InputStreamReader;  
  5.   
  6. public class ReadTxtFile {  
  7.   
  8.     public static void main(String[] args) {  
  9.         try {  
  10.             String charsetName = "UTF-8";   
  11.             String path = "D:/to_delete/test.txt";  
  12.   
  13.             File file = new File(path);  
  14.             if (file.isFile() && file.exists())   
  15.             {  
  16.                 InputStreamReader insReader = new InputStreamReader(  
  17.                         new FileInputStream(file), charsetName);  
  18.   
  19.                 BufferedReader bufReader = new BufferedReader(insReader);  
  20.   
  21.                 String line = new String();  
  22.                 while ((line = bufReader.readLine()) != null) {  
  23.                     System.out.println(line);  
  24.                 }  
  25.                 bufReader.close();  
  26.                 insReader.close();  
  27.             }  
  28.   
  29.         } catch (Exception e) {  
  30.             System.out.println("讀取文件內容操作出錯");  
  31.             e.printStackTrace();  
  32.         }  
  33.     }  
  34.   
  35. }  

 

程序執行結果:
?A中
2國
3
4
5
6

 

我的解決辦法:

使用UltraEdit將上邊的txt文件另存爲UTF-8無BOM格式;

或者

使用Notepad++打開上邊的txt文件執行如下操作“格式-->以UTF-8無BOM格式編碼”,修改後將txt文本進行保存。

 

 

 

網上有篇非常好的文章,論述了問題出現的原因及解決辦法

Java讀帶有BOM的UTF-8文件亂碼原因及解決方法

 

url:http://daimojingdeyu.javaeye.com/blog/397661

 

關鍵字: java 讀utf-8, java寫utf-8, 編碼, utf-8 亂碼

最近在處理文件時發現了同樣類型的文件使用的編碼可能是不同的。所以想將文件的格式統一一下(因爲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格式保存,則在使用程序讀取是會從文件中多讀出一個不可見字符。
測試代碼如下:
Java代碼 複製代碼
  1.   
  2. import java.io.BufferedReader;   
  3. import java.io.File;   
  4. import java.io.FileInputStream;   
  5. import java.io.IOException;   
  6. import java.io.InputStreamReader;   
  7.   
  8.   
  9. public class UTF8Test {   
  10.     public static void main(String[] args) throws IOException {   
  11.         File f  = new File("./utf.txt");   
  12.         FileInputStream in = new FileInputStream(f);   
  13.         // 指定讀取文件時以UTF-8的格式讀取  
  14.         BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));   
  15.            
  16.         String line = br.readLine();   
  17.         while(line != null)   
  18.         {   
  19.             System.out.println(line);   
  20.             line = br.readLine();   
  21.         }   
  22.     }   
  23. }  


utf.txt通過記事本創建,另存時使用指定utf-8編碼,其內容爲:
引用

This is the first line.
This is second line.


正常的測試結果應該是直接輸出utf.txt的文本內容。可是實際上卻輸出了下面的內容:
引用

?This is the first line.
This is second line.

第一行多出了一個問號。
通過上面的幾篇文章應該可以想到是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的方式。

在上面的while循環中可加入下面的代碼,測試一下讀出內容:
Java代碼 複製代碼
  1. byte[] allbytes = line.getBytes("UTF-8");    
  2.             for (int i=0; i < allbytes.length; i++)   
  3.             {   
  4.                 int tmp = allbytes[i];   
  5.                 String hexString = Integer.toHexString(tmp);   
  6.                 // 1個byte變成16進制的,只需要2位就可以表示了,取後面兩位,去掉前面的符號填充  
  7.                 hexString = hexString.substring(hexString.length() -2);   
  8.                 System.out.print(hexString.toUpperCase());   
  9.                 System.out.print(" ");   
  10.             }  


輸出結果如下:
引用

EF BB BF 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 6C 69 6E 65 2E
?This is the first line.
54 68 69 73 20 69 73 20 73 65 63 6F 6E 64 20 6C 69 6E 65 2E
This is second line.


紅色部分的"EF BB BF"剛好是UTF-8文件的BOM編碼,可以看出Java在讀文件時沒能正確處理UTF-8文件的BOM編碼,將前3個字節當作文本內容來處理了。

使用鏈接中提供的代碼可以解決碰到的亂碼問題:
http://koti.mbnet.fi/akini/java/unicodereader/

修改測試代碼中的輸入流後:
Java代碼 複製代碼
  1. BufferedReader br = new BufferedReader(new UnicodeReader(in, Charset.defaultCharset().name()));  

執行,可以看到正確的結果。

將用到的測試代碼及UTF-8讀取亂碼解決(http://koti.mbnet.fi/akini/java/unicodereader)的源碼放在了附件中

 

 

壓縮包中的源代碼

UnicodeInputStream.java

 

  1. /** 
  2.  version: 1.1 / 2007-01-25 
  3.  - changed BOM recognition ordering (longer boms first) 
  4.  
  5.  Original pseudocode   : Thomas Weidenfeller 
  6.  Implementation tweaked: Aki Nieminen 
  7.  
  8.  http://www.unicode.org/unicode/faq/utf_bom.html 
  9.  BOMs in byte length ordering: 
  10.    00 00 FE FF    = UTF-32, big-endian 
  11.    FF FE 00 00    = UTF-32, little-endian 
  12.    EF BB BF       = UTF-8, 
  13.    FE FF          = UTF-16, big-endian 
  14.    FF FE          = UTF-16, little-endian 
  15.  
  16.  Win2k Notepad: 
  17.    Unicode format = UTF-16LE 
  18.  ***/  
  19.   
  20. import java.io.*;  
  21.   
  22. /** 
  23.  * This inputstream will recognize unicode BOM marks and will skip bytes if 
  24.  * getEncoding() method is called before any of the read(...) methods. 
  25.  *  
  26.  * Usage pattern: String enc = "ISO-8859-1"; // or NULL to use systemdefault 
  27.  * FileInputStream fis = new FileInputStream(file); UnicodeInputStream uin = new 
  28.  * UnicodeInputStream(fis, enc); enc = uin.getEncoding(); // check and skip 
  29.  * possible BOM bytes InputStreamReader in; if (enc == null) in = new 
  30.  * InputStreamReader(uin); else in = new InputStreamReader(uin, enc); 
  31.  */  
  32. public class UnicodeInputStream extends InputStream {  
  33.     PushbackInputStream internalIn;  
  34.     boolean isInited = false;  
  35.     String defaultEnc;  
  36.     String encoding;  
  37.   
  38.     private static final int BOM_SIZE = 4;  
  39.   
  40.     UnicodeInputStream(InputStream in, String defaultEnc) {  
  41.         internalIn = new PushbackInputStream(in, BOM_SIZE);  
  42.         this.defaultEnc = defaultEnc;  
  43.     }  
  44.   
  45.     public String getDefaultEncoding() {  
  46.         return defaultEnc;  
  47.     }  
  48.   
  49.     public String getEncoding() {  
  50.         if (!isInited) {  
  51.             try {  
  52.                 init();  
  53.             } catch (IOException ex) {  
  54.                 IllegalStateException ise = new IllegalStateException(  
  55.                         "Init method failed.");  
  56.                 ise.initCause(ise);  
  57.                 throw ise;  
  58.             }  
  59.         }  
  60.         return encoding;  
  61.     }  
  62.   
  63.     /** 
  64.      * Read-ahead four bytes and check for BOM marks. Extra bytes are unread 
  65.      * back to the stream, only BOM bytes are skipped. 
  66.      */  
  67.     protected void init() throws IOException {  
  68.         if (isInited)  
  69.             return;  
  70.   
  71.         byte bom[] = new byte[BOM_SIZE];  
  72.         int n, unread;  
  73.         n = internalIn.read(bom, 0, bom.length);  
  74.   
  75.         if ((bom[0] == (byte0x00) && (bom[1] == (byte0x00)  
  76.                 && (bom[2] == (byte0xFE) && (bom[3] == (byte0xFF)) {  
  77.             encoding = "UTF-32BE";  
  78.             unread = n - 4;  
  79.         } else if ((bom[0] == (byte0xFF) && (bom[1] == (byte0xFE)  
  80.                 && (bom[2] == (byte0x00) && (bom[3] == (byte0x00)) {  
  81.             encoding = "UTF-32LE";  
  82.             unread = n - 4;  
  83.         } else if ((bom[0] == (byte0xEF) && (bom[1] == (byte0xBB)  
  84.                 && (bom[2] == (byte0xBF)) {  
  85.             encoding = "UTF-8";  
  86.             unread = n - 3;  
  87.         } else if ((bom[0] == (byte0xFE) && (bom[1] == (byte0xFF)) {  
  88.             encoding = "UTF-16BE";  
  89.             unread = n - 2;  
  90.         } else if ((bom[0] == (byte0xFF) && (bom[1] == (byte0xFE)) {  
  91.             encoding = "UTF-16LE";  
  92.             unread = n - 2;  
  93.         } else {  
  94.             // Unicode BOM mark not found, unread all bytes  
  95.             encoding = defaultEnc;  
  96.             unread = n;  
  97.         }  
  98.         // System.out.println("read=" + n + ", unread=" + unread);  
  99.   
  100.         if (unread > 0)  
  101.             internalIn.unread(bom, (n - unread), unread);  
  102.   
  103.         isInited = true;  
  104.     }  
  105.   
  106.     public void close() throws IOException {  
  107.         // init();  
  108.         isInited = true;  
  109.         internalIn.close();  
  110.     }  
  111.   
  112.     public int read() throws IOException {  
  113.         // init();  
  114.         isInited = true;  
  115.         return internalIn.read();  
  116.     }  
  117. }  

 

 

UnicodeReader.java

 

 

  1. /** 
  2.  version: 1.1 / 2007-01-25 
  3.  - changed BOM recognition ordering (longer boms first) 
  4.  
  5.  Original pseudocode   : Thomas Weidenfeller 
  6.  Implementation tweaked: Aki Nieminen 
  7.  
  8.  http://www.unicode.org/unicode/faq/utf_bom.html 
  9.  BOMs: 
  10.    00 00 FE FF    = UTF-32, big-endian 
  11.    FF FE 00 00    = UTF-32, little-endian 
  12.    EF BB BF       = UTF-8, 
  13.    FE FF          = UTF-16, big-endian 
  14.    FF FE          = UTF-16, little-endian 
  15.  
  16.  Win2k Notepad: 
  17.    Unicode format = UTF-16LE 
  18.  ***/  
  19.   
  20. import java.io.*;  
  21.   
  22. /** 
  23.  * Generic unicode textreader, which will use BOM mark to identify the encoding 
  24.  * to be used. If BOM is not found then use a given default or system encoding. 
  25.  */  
  26. public class UnicodeReader extends Reader {  
  27.     PushbackInputStream internalIn;  
  28.     InputStreamReader internalIn2 = null;  
  29.     String defaultEnc;  
  30.   
  31.     private static final int BOM_SIZE = 4;  
  32.   
  33.     /** 
  34.      *  
  35.      * @param in 
  36.      *            inputstream to be read 
  37.      * @param defaultEnc 
  38.      *            default encoding if stream does not have BOM marker. Give NULL 
  39.      *            to use system-level default. 
  40.      */  
  41.     UnicodeReader(InputStream in, String defaultEnc) {  
  42.         internalIn = new PushbackInputStream(in, BOM_SIZE);  
  43.         this.defaultEnc = defaultEnc;  
  44.     }  
  45.   
  46.     public String getDefaultEncoding() {  
  47.         return defaultEnc;  
  48.     }  
  49.   
  50.     /** 
  51.      * Get stream encoding or NULL if stream is uninitialized. Call init() or 
  52.      * read() method to initialize it. 
  53.      */  
  54.     public String getEncoding() {  
  55.         if (internalIn2 == null)  
  56.             return null;  
  57.         return internalIn2.getEncoding();  
  58.     }  
  59.   
  60.     /** 
  61.      * Read-ahead four bytes and check for BOM marks. Extra bytes are unread 
  62.      * back to the stream, only BOM bytes are skipped. 
  63.      */  
  64.     protected void init() throws IOException {  
  65.         if (internalIn2 != null)  
  66.             return;  
  67.   
  68.         String encoding;  
  69.         byte bom[] = new byte[BOM_SIZE];  
  70.         int n, unread;  
  71.         n = internalIn.read(bom, 0, bom.length);  
  72.   
  73.         if ((bom[0] == (byte0x00) && (bom[1] == (byte0x00)  
  74.                 && (bom[2] == (byte0xFE) && (bom[3] == (byte0xFF)) {  
  75.             encoding = "UTF-32BE";  
  76.             unread = n - 4;  
  77.         } else if ((bom[0] == (byte0xFF) && (bom[1] == (byte0xFE)  
  78.                 && (bom[2] == (byte0x00) && (bom[3] == (byte0x00)) {  
  79.             encoding = "UTF-32LE";  
  80.             unread = n - 4;  
  81.         } else if ((bom[0] == (byte0xEF) && (bom[1] == (byte0xBB)  
  82.                 && (bom[2] == (byte0xBF)) {  
  83.             encoding = "UTF-8";  
  84.             unread = n - 3;  
  85.         } else if ((bom[0] == (byte0xFE) && (bom[1] == (byte0xFF)) {  
  86.             encoding = "UTF-16BE";  
  87.             unread = n - 2;  
  88.         } else if ((bom[0] == (byte0xFF) && (bom[1] == (byte0xFE)) {  
  89.             encoding = "UTF-16LE";  
  90.             unread = n - 2;  
  91.         } else {  
  92.             // Unicode BOM mark not found, unread all bytes  
  93.             encoding = defaultEnc;  
  94.             unread = n;  
  95.         }  
  96.         // System.out.println("read=" + n + ", unread=" + unread);  
  97.   
  98.         if (unread > 0)  
  99.             internalIn.unread(bom, (n - unread), unread);  
  100.   
  101.         // Use given encoding  
  102.         if (encoding == null) {  
  103.             internalIn2 = new InputStreamReader(internalIn);  
  104.         } else {  
  105.             internalIn2 = new InputStreamReader(internalIn, encoding);  
  106.         }  
  107.     }  
  108.   
  109.     public void close() throws IOException {  
  110.         init();  
  111.         internalIn2.close();  
  112.     }  
  113.   
  114.     public int read(char[] cbuf, int off, int len) throws IOException {  
  115.         init();  
  116.         return internalIn2.read(cbuf, off, len);  
  117.     }  
  118.   
  119. }  

 

UTF8Test.java

 

  1. import java.io.BufferedReader;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStreamReader;  
  6. import java.nio.charset.Charset;  
  7.   
  8.   
  9. public class UTF8Test {  
  10.     public static void main(String[] args) throws IOException {  
  11.         File f  = new File("./utf.txt");  
  12.         FileInputStream in = new FileInputStream(f);  
  13.         // 指定讀取文件時以UTF-8的格式讀取  
  14. //      BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));  
  15.         BufferedReader br = new BufferedReader(new UnicodeReader(in, Charset.defaultCharset().name()));  
  16.           
  17.         String line = br.readLine();  
  18.         while(line != null)  
  19.         {  
  20.             System.out.println(line);  
  21.             line = br.readLine();  
  22.         }  
  23.     }  
  24. }  

  來源:http://blog.csdn.net/jackpk/article/details/5702964


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