我们知道从一个文件流中读取内容时是要指定具体的编码格式的,否则读出来的内容会是乱码。比如我们的代码写成下面这个样子:
private static void m1(){ try(FileInputStream fileInputStream = new FileInputStream("D://每日摘录.txt")) { byte[] bytes = FileCopyUtils.copyToByteArray(fileInputStream); System.out.println(new String(bytes)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
执行上面的代码,有时我们能“侥幸”得到正确的执行结果。因为 new String(byte[])
这个方法会指定默认的编码格式,所以如果我们读取的文件的编码格式正好是UTF8的话,那上面的代码就一点问题没有。但是如果我们读取的是一个编码格式是GBK的文件,那么得到的内容将是一坨乱码。
上面的问题解决起来很简单,只要指定下字符编码就可以了。
new String(bytes,"GBK");
在告知文件编码格式的条件下,解决上面的问题是很简单。假如现在没告知文件具体的编码格式,我们需要怎么正确的读取文件呢?一个可行的办法是推测文件编码方式。
网上有多种方式可以“推测”出一个文件的可用编码,但是需要注意的是: 所有的方法都不能保证推测出来的结果是绝对准确的,有的方法推测的准确率较高,而有的方法推测出来的准确率较低 。主要的推测方法有以下几种:
下面就来具体介绍下怎么使用 cpdector
和 ICU4J
推测文件编码。
使用Cpdetector jar包,提供两种方式检测文件编码,至于选择哪种 需要根据个人需求,文档有注释。依赖antlr-2.7.4.jar,chardet-1.0.jar,jargs-1.0.jar三个jar包。 可以再官网下载 http://cpdetector.sourceforge.net/。
import info.monitorenter.cpdetector.io.ASCIIDetector; import info.monitorenter.cpdetector.io.ByteOrderMarkDetector; import info.monitorenter.cpdetector.io.CodepageDetectorProxy; import info.monitorenter.cpdetector.io.JChardetFacade; import info.monitorenter.cpdetector.io.ParsingDetector; import info.monitorenter.cpdetector.io.UnicodeDetector; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import org.apache.log4j.Logger; /** * <p> * 获取流编码,不保证完全正确,设置检测策略 isFast为true为快速检测策略,false为正常检测 * InputStream 支持mark,则会在检测后调用reset,外部可重新使用。 * InputStream 流没有关闭。 * </p> * * <p> * 如果采用快速检测编码方式,最多会扫描8个字节,依次采用的{@link UnicodeDetector},{@link byteOrderMarkDetector}, * {@link JChardetFacade}, {@link ASCIIDetector}检测。对于一些标准的unicode编码,适合这个方式或者对耗时敏感的。 * </p> * * <p> * 采用正常检测,读取指定字节数,如果没有指定,默认读取全部字节检测,依次采用的{@link byteOrderMarkDetector},{@link parsingDetector},{@link JChardetFacade}, {@link ASCIIDetector}检测。 * 字节越多检测时间越长,正确率较高。 * </p> * @author WuKong * */ public class CpdetectorEncoding { private static final Logger logger = Logger.getLogger(CpdetectorEncoding.class); /** * <p> * 获取流编码,不保证完全正确,设置检测策略 isFast为true为快速检测策略,false为正常检测 * InputStream 支持mark,则会在检测后调用reset,外部可重新使用。 * InputStream 流没有关闭。 * </p> * * <p> * 如果采用快速检测编码方式,最多会扫描8个字节,依次采用的{@link UnicodeDetector},{@link byteOrderMarkDetector}, * {@link JChardetFacade}, {@link ASCIIDetector}检测。对于一些标准的unicode编码,适合这个方式或者对耗时敏感的。 * </p> * * <p> * 采用正常检测,读取指定字节数,如果没有指定,默认读取全部字节检测,依次采用的{@link byteOrderMarkDetector},{@link parsingDetector},{@link JChardetFacade}, {@link ASCIIDetector}检测。 * 字节越多检测时间越长,正确率较高。 * </p> * * @param in 输入流 isFast 是否采用快速检测编码方式 * @return Charset The character are now - hopefully - correct。如果为null,没有检测出来。 * @throws IOException */ public Charset getEncoding(InputStream buffIn,boolean isFast) throws IOException{ return getEncoding(buffIn,buffIn.available(),isFast); } public Charset getFastEncoding(InputStream buffIn) throws IOException{ return getEncoding(buffIn,MAX_READBYTE_FAST,DEFALUT_DETECT_STRATEGY); } public Charset getEncoding(InputStream in, int size, boolean isFast) throws IOException { try { java.nio.charset.Charset charset = null; int tmpSize = in.available(); size = size >tmpSize?tmpSize:size; //if in support mark method, if(in.markSupported()){ if(isFast){ size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size; in.mark(size++); charset = getFastDetector().detectCodepage(in, size); }else{ in.mark(size++); charset = getDetector().detectCodepage(in, size); } in.reset(); }else{ if(isFast){ size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size; charset = getFastDetector().detectCodepage(in, size); }else{ charset = getDetector().detectCodepage(in, size); } } return charset; }catch(IllegalArgumentException e){ logger.error(e.getMessage(),e); throw e; } catch (IOException e) { logger.error(e.getMessage(),e); throw e; } } public Charset getEncoding(byte[] byteArr,boolean isFast) throws IOException{ return getEncoding(byteArr, byteArr.length, isFast); } public Charset getFastEncoding(byte[] byteArr) throws IOException{ return getEncoding(byteArr, MAX_READBYTE_FAST, DEFALUT_DETECT_STRATEGY); } public Charset getEncoding(byte[] byteArr, int size,boolean isFast) throws IOException { size = byteArr.length>size?size:byteArr.length; if(isFast){ size = size>MAX_READBYTE_FAST?MAX_READBYTE_FAST:size; } ByteArrayInputStream byteArrIn = new ByteArrayInputStream(byteArr,0,size); BufferedInputStream in = new BufferedInputStream(byteArrIn); try { Charset charset = null; if(isFast){ charset = getFastDetector().detectCodepage(in, size); }else{ charset = getDetector().detectCodepage(in, size); } return charset; } catch (IllegalArgumentException e) { logger.error(e.getMessage(),e); throw e; } catch (IOException e) { logger.error(e.getMessage(),e); throw e; } } private static CodepageDetectorProxy detector =null; private static CodepageDetectorProxy fastDtector =null; private static ParsingDetector parsingDetector = new ParsingDetector(false); private static ByteOrderMarkDetector byteOrderMarkDetector = new ByteOrderMarkDetector(); //default strategy use fastDtector private static final boolean DEFALUT_DETECT_STRATEGY = true; private static final int MAX_READBYTE_FAST = 8; private static CodepageDetectorProxy getDetector(){ if(detector==null){ detector = CodepageDetectorProxy.getInstance(); // Add the implementations of info.monitorenter.cpdetector.io.ICodepageDetector: // This one is quick if we deal with unicode codepages: detector.add(byteOrderMarkDetector); // The first instance delegated to tries to detect the meta charset attribut in html pages. detector.add(parsingDetector); // This one does the tricks of exclusion and frequency detection, if first implementation is // unsuccessful: detector.add(JChardetFacade.getInstance()); detector.add(ASCIIDetector.getInstance()); } return detector; } private static CodepageDetectorProxy getFastDetector(){ if(fastDtector==null){ fastDtector = CodepageDetectorProxy.getInstance(); fastDtector.add(UnicodeDetector.getInstance()); fastDtector.add(byteOrderMarkDetector); fastDtector.add(JChardetFacade.getInstance()); fastDtector.add(ASCIIDetector.getInstance()); } return fastDtector; } }
ICU (International Components for Unicode)是为软件应用提供Unicode和全球化支持的一套成熟、广泛使用的C/C++和Java类库集,可在所有平台的C/C++和Java软件上获得一致的结果。
ICU首先是由Taligent公司开发的,Taligent公司被合并为IBM公司全球化认证中心的Unicode研究组后,ICU由IBM和开源组织合作继续开发。开始ICU只有Java平台的版本,后来这个平台下的ICU类被吸纳入SUN公司开发的JDK1.1,并在JDK以后的版本中不断改进。C++和C平台下的ICU是由JAVA平台下的ICU移植过来的,移植过的版本被称为ICU4C,来支持这C/C++两个平台下的国际化应用。ICU4J和ICU4C区别不大,但由于ICU4C是开源的,并且紧密跟进Unicode标准,ICU4C支持的Unicode标准总是最新的;同时,因为JAVA平台的ICU4J的发布需要和JDK绑定,ICU4C支持Unicode标准改变的速度要比ICU4J快的多。
ICU的功能主要有:
代码示例:
public class FileEncodingDetector { public static void main(String[] args) { File file = new File("D://xx1.log"); System.out.println(getFileCharsetByICU4J(file)); } public static String getFileCharsetByICU4J(File file) { String encoding = null; try { Path path = Paths.get(file.getPath()); byte[] data = Files.readAllBytes(path); CharsetDetector detector = new CharsetDetector(); detector.setText(data); //这个方法推测首选的文件编码格式 CharsetMatch match = detector.detect(); //这个方法可以推测出所有可能的编码方式 CharsetMatch[] charsetMatches = detector.detectAll(); if (match == null) { return encoding; } encoding = match.getName(); } catch (IOException var6) { System.out.println(var6.getStackTrace()); } return encoding; } }
ICU4J介绍
Cpdetector
基于Cpdetector 检测文件编码