這里研究一下如何來判斷文件的編碼是否是UTF-8,關於這個問題網絡上一般采用的是判斷文件的BOM頭,但是這種方法有個缺點,就是有一些工具,比如EditPlus,比如Java程序,做出來的UTF-8編碼的文件是不會在文件內容的前面加上BOM頭的,對於這種情況,網絡上的這個辦法就會檢測失敗。
在經過一些測試之后,研究了一個解決方案。
考慮如下文件輸入流的代碼,
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
File f = new File(fn);
fis = new FileInputStream(f);
isr = new InputStreamReader(fis, "UTF-8");
br = new BufferedReader(isr);
推測執行原理如下,(都是根據測試結果的猜測)
1。fis 根據文件的保存編碼來采用不同的編碼讀取文件。讀取結果為byte[]
2.isr設定的話,那么根據isr設定的編碼來讀取文件,如果不設定,那么編碼采用系統默認編碼 ansi(window-31j,shift_jis)
3。br.readline,將isr的結果組合為String,如果isr沒有設定編碼,那么組合String時采用的編碼為系統默認編碼 ansi(window-31j,shift_jis),如果isr設定了編碼,那么采用isr設定好的編碼。
4。new string(byte[],"encode") 根據指定編碼生成string,如果不指定,采用系統默認編碼。系統默認編碼 ansi
5。string.getbyte("encode") 從String根據特定的編碼取得byte[]
問題出在第1步,第一步fis因為讀取文件的時候,調用的是native,也就是系統(windows系統)的東西,他用了系統的東西,系統的這個東西作了編碼判斷,但是因為他調用的是native的東西,這個判定結果沒有返回給java,導致java里面isr,br沒有辦法跟fis協調一致,isr,br只能采用系統默認編碼 ansi(window-31j,shift_jis),而不是采用fis的判定結果來讀取文件。
這導致了,當文件使用ansi編碼保存的時候,默認編碼跟fis判定結果一致,不會出任何問題。
當文件使用了utf-8編碼的時候,默認編碼ansi,跟fis判定結果utf-8不一致,fis采用uft-8編碼讀取出文件內容,而后,br.readline采用系統默認編碼把UTF-8編碼對應的byte[]組合成了ansi編碼對應的字符串,就產生了亂碼。
我在網絡以及java api里面查找了一下,沒有找到判定文件保存編碼的方法。推論:因為java是調用了native的方法來實際讀取文件的,判定在native里面完成,但是沒有把判定結果公開給我們程序員。
另有一個測試結果的推論,英文字符在任何編碼下面讀取出來的byte[]都是一樣的。因為我們才用任何編碼都不會出現英文字符亂碼的問題,所以大多數時候這個判定對我們沒有影響,這里不討論特殊情況下因為這個原因造成的影響。
根據以上推論,考慮如下解決問題的思路,
1。通過fis來讀取文件,這個時候讀取來的byte[]根據文件的保存格式是不同的。fis會自動判斷處理。
2。通過br來讀取文件。代碼示例如下:
private static void readTest(String fn){
BufferedReader br = null;
InputStreamReader isr = null;
FileInputStream fis = null;
try{
File f = new File(fn);
fis = new FileInputStream(f);
isr = new InputStreamReader(fis, "UTF-8");
br = new BufferedReader(isr);
String s = null;
while ((s = br.readLine()) != null) {
byte[] buf = s.getBytes("UTF-8");
for(int j=0; j<buf.length; j++){
System.out.println(buf[j]);
}
}
} catch(Exception e){
e.printStackTrace();
} finally{
try{
fis.close();
isr.close();
} catch(Exception e){
e.printStackTrace();
}
}
}
3。1,2的讀取結果byte[]進行比較,如果相同,那么可以認為文件的保存格式為UTF-8(雖然存在全英文ansi保存的可能,但是這種狀況認為是utf-8保存不會有影響),如果不同則不是UTF-8,考慮我們目前狀況,那么不是UTF-8可以認為文件保存編碼就是ANSI,如果不可以這么認為,其他編碼類型也要做這個判斷。因為英文字符在任何編碼下面讀取出來的byte[]都是一樣的。所以這里有一個效率問題,必須文件內容全部比較,不能只比較一部分。
以上推測經測試有效,沒有問題。
如果使用第三方開源代碼common-io.jar的話,可以將以上思路簡化為如下代碼。
public boolean isUTF8(File file){
try {
byte[] buf = FileUtils.readFileToByteArray(file);
String filecCntent = FileUtils.readFileToString(file,"UTF-8");
if(filecCntent.equals(new String(buf,"UTF-8"))){
return true;
}
} catch (IOException e) {
// TODO 動生成された catch ブロック
e.printStackTrace();
}
return false;
}
這個判定有一個效率問題,在這個文章中采用的是讀取整個文件,如果我們文件太大,會比較花時間。這種情況可以考慮按行來讀取判定,某一行不一致就可以退出了。這樣可以提高一些效率。