参考以下线程: Java App:无法正确读取iso-8859-1编码文件
以编程方式确定输入流/文件的正确字符集编码的最佳方法是什么?
我尝试过使用以下内容:
File in = new File(args[0]); InputStreamReader r = new InputStreamReader(new FileInputStream(in)); System.out.println(r.getEncoding());
但是在我知道用ISO8859_1编码的文件中,上面的代码产生了ASCII,这是不正确的,并且不允许我正确地将文件的内容呈现回控制台.
您无法确定任意字节流的编码.这是编码的本质.编码意味着字节值与其表示之间的映射.所以每个编码"都可能"是正确的.
的getEncoding()方法将返回其设置(读取的编码的JavaDoc),用于该流.它不会猜测你的编码.
一些流告诉您使用哪种编码来创建它们:XML,HTML.但不是任意字节流.
无论如何,如果必须,您可以尝试自己猜测编码.每种语言都有一个共同的频率.在英语中,char e经常出现,但ê似乎很少出现.在ISO-8859-1流中,通常没有0x00字符.但是UTF-16流有很多.
或者:你可以问用户.我已经看过应用程序以不同的编码为您呈现文件的片段,并要求您选择"正确"的文件.
我使用过这个库,类似于jchardet,用于检测Java中的编码:http: //code.google.com/p/juniversalchardet/
看看这个: http://site.icu-project.org/(icu4j)他们有用于检测来自IOStream的字符集的库可能很简单,如下所示:
BufferedInputStream bis = new BufferedInputStream(input); CharsetDetector cd = new CharsetDetector(); cd.setText(bis); CharsetMatch cm = cd.detect(); if (cm != null) { reader = cm.getReader(); charset = cm.getName(); }else { throw new UnsupportedCharsetException() }
这是我的最爱:
TikaEncodingDetector
相关性:
org.apache.any23 apache-any23-encoding 1.1
样品:
public static Charset guessCharset(InputStream is) throws IOException { return Charset.forName(new TikaEncodingDetector().guessEncoding(is)); }
GuessEncoding
相关性:
org.codehaus.guessencoding guessencoding 1.4 jar
样品:
public static Charset guessCharset2(File file) throws IOException { return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8); }
您当然可以通过使用a 解码并注意"malformed-input"或"unmappable-character"错误来验证特定字符集的文件.当然,这只会告诉你字符集是否错误; 它没有告诉你它是否正确.为此,您需要一个比较基础来评估解码结果,例如,您是否事先知道字符是否限于某个子集,或者文本是否遵循某种严格的格式?最重要的是,字符集检测是猜测而没有任何保证.CharsetDecoder
在撰写本文时,它们出现了三个库:
GuessEncoding
ICU4J
juniversalchardet
我没有包含Apache Any23,因为它使用了ICU4j 3.4.
不可能对上面每个库检测到的字符集进行认证.但是,可以依次询问他们并对返回的回复进行评分.
每个响应可以分配一个点.响应越多,检测到的字符集就越有信心.这是一种简单的评分方法.你可以详细说明别人.
以下是实现前几行中描述的策略的完整代码段.
public static String guessEncoding(InputStream input) throws IOException { // Load input data long count = 0; int n = 0, EOF = -1; byte[] buffer = new byte[4096]; ByteArrayOutputStream output = new ByteArrayOutputStream(); while ((EOF != (n = input.read(buffer))) && (count <= Integer.MAX_VALUE)) { output.write(buffer, 0, n); count += n; } if (count > Integer.MAX_VALUE) { throw new RuntimeException("Inputstream too large."); } byte[] data = output.toByteArray(); // Detect encoding MapencodingsScores = new HashMap<>(); // * GuessEncoding updateEncodingsScores(encodingsScores, new CharsetToolkit(data).guessEncoding().displayName()); // * ICU4j CharsetDetector charsetDetector = new CharsetDetector(); charsetDetector.setText(data); charsetDetector.enableInputFilter(true); CharsetMatch cm = charsetDetector.detect(); if (cm != null) { updateEncodingsScores(encodingsScores, cm.getName()); } // * juniversalchardset UniversalDetector universalDetector = new UniversalDetector(null); universalDetector.handleData(data, 0, data.length); universalDetector.dataEnd(); String encodingName = universalDetector.getDetectedCharset(); if (encodingName != null) { updateEncodingsScores(encodingsScores, encodingName); } // Find winning encoding Map.Entry maxEntry = null; for (Map.Entry e : encodingsScores.entrySet()) { if (maxEntry == null || (e.getValue()[0] > maxEntry.getValue()[0])) { maxEntry = e; } } String winningEncoding = maxEntry.getKey(); //dumpEncodingsScores(encodingsScores); return winningEncoding; } private static void updateEncodingsScores(Map encodingsScores, String encoding) { String encodingName = encoding.toLowerCase(); int[] encodingScore = encodingsScores.get(encodingName); if (encodingScore == null) { encodingsScores.put(encodingName, new int[] { 1 }); } else { encodingScore[0]++; } } private static void dumpEncodingsScores(Map encodingsScores) { System.out.println(toString(encodingsScores)); } private static String toString(Map encodingsScores) { String GLUE = ", "; StringBuilder sb = new StringBuilder(); for (Map.Entry e : encodingsScores.entrySet()) { sb.append(e.getKey() + ":" + e.getValue()[0] + GLUE); } int len = sb.length(); sb.delete(len - GLUE.length(), len); return "{ " + sb.toString() + " }"; }
改进:
该guessEncoding
方法完全读取输入流.对于大型输入流,这可能是一个问题.所有这些库都将读取整个输入流.这意味着用于检测字符集的大量时间消耗.
可以将初始数据加载限制为几个字节,并仅对这几个字节执行字符集检测.
上面的库是简单的BOM检测器,当然只有在文件开头有BOM时才能使用.看一下扫描文本的http://jchardet.sourceforge.net/
我找到了一个很好的第三方库,它可以检测实际编码:http: //glaforge.free.fr/wiki/index.php?wiki = GuessEncoding
我没有广泛测试它,但它似乎工作.
如果您使用ICU4J(http://icu-project.org/apiref/icu4j/)
这是我的代码:
String charset = "ISO-8859-1"; //Default chartset, put whatever you want byte[] fileContent = null; FileInputStream fin = null; //create FileInputStream object fin = new FileInputStream(file.getPath()); /* * Create byte array large enough to hold the content of the file. * Use File.length to determine size of the file in bytes. */ fileContent = new byte[(int) file.length()]; /* * To read content of the file in byte array, use * int read(byte[] byteArray) method of java FileInputStream class. * */ fin.read(fileContent); byte[] data = fileContent; CharsetDetector detector = new CharsetDetector(); detector.setText(data); CharsetMatch cm = detector.detect(); if (cm != null) { int confidence = cm.getConfidence(); System.out.println("Encoding: " + cm.getName() + " - Confidence: " + confidence + "%"); //Here you have the encode name and the confidence //In my case if the confidence is > 50 I return the encode, else I return the default value if (confidence > 50) { charset = cm.getName(); } }
记得把所有尝试捕获需要它.
我希望这适合你.
据我所知,在这种情况下没有通用的库适合所有类型的问题。因此,对于每个问题,您都应该测试现有的库并选择可以满足您的问题约束的最佳库,但是通常都不适合。在这些情况下,您可以编写自己的编码检测器!正如我写的...
我编写了一个元Java工具,使用IBM ICU4j和Mozilla JCharDet作为内置组件来检测HTML网页的字符集编码。在这里您可以找到我的工具,请先阅读自述文件部分。另外,您可以在我的论文及其参考文献中找到此问题的一些基本概念。
在贝娄,我提供了一些有益的意见,这些意见对我的工作很有帮助:
字符集检测不是一个万无一失的过程,因为它基本上是基于统计数据的,而实际发生的事情是猜测没有检测到
在这种情况下,icu4j是IBM imho的主要工具
TikaEncodingDetector和Lucene-ICU4j都使用了icu4j,它们的准确性与我的测试中的icu4j并没有太大的区别(我记得最多为%1)
icu4j比jchardet通用得多,icu4j稍微偏向于IBM系列编码,而jchardet偏向于utf-8
由于UTF-8在HTML世界中的广泛使用;总体而言,jchardet是比icu4j更好的选择,但不是最佳选择!
icu4j非常适合东亚特定的编码,例如EUC-KR,EUC-JP,SHIFT_JIS,BIG5和GB系列编码
icu4j和jchardet都无法处理Windows-1251和Windows-1256编码的HTML页面。Windows-1251 aka cp1251广泛用于基于西里尔文的语言,例如俄语,而Windows-1256 aka cp1256广泛用于阿拉伯语
几乎所有的编码检测工具都使用统计方法,因此输出的准确性很大程度上取决于输入的大小和内容
某些编码本质上是相同的,只是存在部分差异,因此在某些情况下,猜测或检测到的编码可能为假,但同时为真!关于Windows-1252和ISO-8859-1。(请参阅我的论文5.2节的最后一段)