这里研究一下如何来判断文件的编码是否是 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 ;
}
这个判定有一个效率问题,在这个文章中采用的是读取整个文件,如 果我们文件太大,会比较花时间。这种情况可以考虑按行来读取判定,某一行不一致就可以退出了。这样可以提高一些效率。