UTF-8 file when bom exist



java普通的文件读取方式对于bom是无法正常识别的。

   使用普通的InputStreamReader,如果采用的编码正确,那么可以获得正确的字符,但bom仍然附带在结果中,很容易导致数据处理出错。
另外,对于存在BOM头的文件,无法猜测它使用的编码。

目标:

实现一种方式,可对BOM头进行捕捉和过滤


解决方案有二:

一、  使用apache的工具类,以BOMStream为例:

    BOMStream,api参考:http://commons.apache.org/io/apidocs/org/apache/commons/io/input/BOMInputStream.html

    该类的构造方式:
   BOMInputStream bomIn = new BOMInputStream(in) //仅能检测到UTF8的bom,且在流中exclude掉bom
   
   BOMInputStream bomIn = new BOMInputStream(in, include); //同上,且指定是否包含

    也可以指定检测多种编码的bom,但目前仅支持UTF-8/UTF-16LE/UTF-16BE三种,对于UTF32之类不支持。
   BOMInputStream bomIn = new BOMInputStream(in, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);
   
    有用的方法:
   bomIn.hasBOM()、hasBOM(ByteOrderMask.**)可用于判断当前流中是否检测到了bom。

   读取文件示例:
  
  1. FileInputStream fis = new FileInputStream(file);  
  2.   //可检测多种类型,并剔除bom  
  3.   BOMInputStream bomIn = new BOMInputStream(in, false,ByteOrderMark.UTF-8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);  
  4.   String charset = "utf-8";  
  5.   //若检测到bom,则使用bom对应的编码  
  6.   if(bomIn.hasBOM()){  
  7.      charset = bomIn.bs.getBOMCharsetName();  
  8.   }  
  9.   InputStreamReader reader = new InputStreamReader(bomIn, charset);  
  10.   ...  
 FileInputStream fis = new FileInputStream(file);
   //可检测多种类型,并剔除bom
   BOMInputStream bomIn = new BOMInputStream(in, false,ByteOrderMark.UTF-8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE);
   String charset = "utf-8";
   //若检测到bom,则使用bom对应的编码
   if(bomIn.hasBOM()){
      charset = bomIn.bs.getBOMCharsetName();
   }
   InputStreamReader reader = new InputStreamReader(bomIn, charset);
   ...


二、使用一个更强大点的工具类(可以支持UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE):
       

   参考地址:http://koti.mbnet.fi/akini/java/unicodereader/, 下载其中两个文件:UnicodeStream和UnicodeReader

   以UnicodeReader为例:

  1. FileInputStream fis = new FileInputStream(file);  
  2. UnicodeReader ur = new UnicodeReader(fis, "utf-8");  
  3. BufferedReader br = new BufferedReader(ur);  
  4. ...  
   FileInputStream fis = new FileInputStream(file);
   UnicodeReader ur = new UnicodeReader(fis, "utf-8");
   BufferedReader br = new BufferedReader(ur);
   ...

   相较于Apache的工具类,这里的UnicodeReader 支持更多的BOM编码。

   源码解读:

   UnicodeReader 通过PushbackInputStream+InputStreamReader实现BOM的自动检测和过滤读取;

   当没有检测到BOM时,pushback流将回退,并采用构造函数传入的编码进行读取。
   否则使用BOM对应的编码进行读取。

   

  相对来说,第二种方式更加轻量和强大;另外也更加透明,可以随便修改源码来实现自己的需求。


2


最近在处理文件时发现了同样类型的文件使用的编码可能是不同的。所以想将文件的格式统一一下(因为UTF-8的通用性,决定往UTF-8统一),遇见的第一个问题是:如何查看现有文件的编码方式。

在java中,class文件采用utf8的编码方式,JVM运行时采用utf16。Java的字符串是永远都是unicode的,采用的是UTF-16的编码方式。

想测试一下,java对UTF-8文件的读写的能力,结果发现了一个很郁闷的问题,如果通过java写的UTF-8文件,使用Java可以正确的读,但是如果用记事本将相同的内容使用UTF-8格式保存,则在使用程序读取是会从文件中多读出一个不可见字符。

测试代码如下:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;


public class UTF8Test {
 public static void main(String[] args) throws IOException {
  File f  = new File("./utf.txt");
  FileInputStream in = new FileInputStream(f);
  // 指定读取文件时以UTF-8的格式读取
  BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
 
  String line = br.readLine();
  while(line != null)
  {
   System.out.println(line);
   line = br.readLine();
  }
 }
}

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循环中可加入下面的代码,测试一下读出内容:

byte[] allbytes = line.getBytes("UTF-8");
   for (int i=0; i < allbytes.length; i++)
   {
    int tmp = allbytes[i];
    String hexString = Integer.toHexString(tmp);
    // 1个byte变成16进制的,只需要2位就可以表示了,取后面两位,去掉前面的符号填充
    hexString = hexString.substring(hexString.length() -2);
    System.out.print(hexString.toUpperCase());
    System.out.print(" ");
   }

输出结果如下:

引用

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/

修改测试代码中的输入流后:

BufferedReader br = new BufferedReader(new UnicodeReader(in, Charset.defaultCharset().name()));

执行,可以看到正确的结果。

将用到的测试代码及UTF-8读取乱码解决的源码放在了附件中

本篇文章来源于 Linux公社网站(www.linuxidc.com)  原文链接:http://www.linuxidc.com/Linux/2012-12/76707.htm

"error:--resume checkpoint does not exist"表示在程序运行过程中出现了错误,提示指定的checkpoint(检查点)不存在。 checkpoint是一种保存程序运行状态的机制,在许多应用中可用于保存模型参数、中间结果等。当程序需要恢复之前的运行状态时,可以使用checkpoint来实现。然而,如果指定的checkpoint不存在,程序就无法正确地恢复之前的状态,会出现错误。 出现这个错误的原因可能有以下几种: 1. 指定的checkpoint路径不正确:可能是路径被拼写错误或者路径不存在。需要仔细检查输入的路径是否正确,并确认路径下是否存在指定的checkpoint文件。 2. checkpoint文件被删除或移动:如果在程序运行期间手动删除或移动了checkpoint文件,那么程序就无法找到指定的checkpoint。此时,可以尝试找回或重新生成checkpoint文件。 3. 程序中指定的checkpoint名称与实际的名称不一致:可能是程序中指定的checkpoint名称与实际生成的名称不符合。需要确保指定的checkpoint名称正确。 针对这个错误,可以采取以下一些解决措施: 1. 检查路径和文件名:仔细检查checkpoint路径和文件名是否正确,并确保路径下存在指定的checkpoint文件。 2. 恢复或重新生成checkpoint文件:如果checkpoint文件被删除或移动,可以尝试找回原有的checkpoint文件或重新生成checkpoint文件。 3. 确认指定的checkpoint名称是否正确:确保程序中指定的checkpoint名称与实际生成的名称一致。 总之,出现"error:--resume checkpoint does not exist"错误说明在程序运行过程中找不到指定的checkpoint文件。通过仔细检查路径、文件名和名称的正确性,并采取相应的恢复或重新生成措施,可以解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值