大道至简,知易行难
广阔天地,大有作为

使用Java猜测或检测文本编码(Encoding detection),基于juniversalchardet和jchardet方案

摘要:本文介绍了在Java环境中对未知编码的文本或字符串进行检测/猜测的方法,分别给出了遍历和基于Mozilla Charset Detection及Mozilla Universal Charset Detection的两大类解决方案,给出了使用Mozilla Charset Detection及Mozilla Universal Charset Detection的几乎全部历史细节,并给出详细代码。值得一提的是,本文指出了Mozilla Universal Charset Detection的一个Java实现(juniversalchardet)在处理短文本时的不足。
 
在日常工作中,有时我们需要猜测某些文件或本文的编码(Encoding detection)。例如,考虑如下的字节数组(第一个数组是第二个数组的一部分,未加粗的为ASCII字符):
byte[] buf = new byte[] { -78, -35, -63, -15, -55, -25, -123, 94 };
byte[] buf = new byte[] { 49, 48, 50, 52, -78, -35, -63, -15, -55, -25, -123, 94, 32, 116, 54, 54, 121, 46, 99, 111, 109, 46, 106, 112, 103 };
直接使用UTF-8解析时结果为乱码,当涉及多语种时(如日文、韩文、俄文等)情况更加难以判断。那么,在没有任何背景的情况如何判断一个文件或一段文本的编码呢?
一、偶然需要检测编码情况
在偶然情况下,当需要猜测一个文件或一段文本的编码时,我们可以使用所有的编码,即Charset.availableCharsets()对要猜测的文件或文本进行一次遍历,筛选出正确的编码。例如:
for (Map.Entry<String, Charset> ent : Charset.availableCharsets().entrySet())
{
System.out.print(ent.getKey());
CharBuffer charBuffer;
try
{
charBuffer = ent.getValue().newDecoder().decode(ByteBuffer.wrap(data));
}
catch (Exception e)
{
System.out.println(” failed when decoding.”);
System.out.println();
continue;
}
System.out.print(” -> “);
System.out.append(charBuffer);
System.out.println();
}
通过运行上述的代码,我们可以观察到GBK、GB18030等编码格式是正确的:
遍历所有可用编码来判断编码方式

遍历所有可用编码来判断编码方式

 
既然这里提到了GB2312、GBK、GB18030,那么不妨明确下GB2312、GBK、GB18030及BIG5的联系和区别:
①GB2312,中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,由中国国家标准总局发布,1981年5月1日实施。GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符,对任意一个图形字符都采用两个字节表示。GB2312基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。但对于繁体、人名、古汉语、少数民族文字等方面出现的罕用字,GB2312不能处理,导致了后来GBK及GB18030汉字字符集的出现。
②GBK,Chinese Internal Code Specification,汉字内码扩展规范,K为汉语拼音Kuo Zhan(扩展)中“扩”字的声母。GBK共收录21886个汉字和图形符号,包括:GB2312中的全部汉字和非汉字符号、BIG5中的全部汉字、与 ISO10646相应的国家标准GB13000中的其它CJK汉字、其它汉字/部首/符号。GBK向下与GB2312完全兼容,向上支持ISO10646国际标准,在前者向后者过渡过程中起到的承上启下的作用。采用双字节表示。
③GB18030,国家标准GB18030-2005《信息技术中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB18030-2000《信息技术信息交换用汉字编码字符集基本集的扩充》的修订版。GB18030与GB2312-1980和GBK兼容,共收录汉字70244个。GB18030与UTF-8 相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。编码空间庞大,最多可定义161万个字符。支持中国国内少数民族的文字,汉字收录范围包含繁体汉字以及日韩汉字。
④BIG5,通行于台湾、香港地区的一个繁体中文编码方案。
然而,在生产环境中,尤其是在多语言语种的情况下,使用这种遍历的方法来判断编码方式显然是不允许的。
二、生产环境中进行编码检测
2.1 Mozilla Charset Detection及Mozilla Universal Charset Detection
在编码检测方面,Mozilla浏览器有两个历史悠久的模块(分别为chardet和universialchardet)与编码检测有关,用于检测网页的编码,分别参见如下两个链接。其中,第二个链接为著名的一篇论文《A composite approach to language/encoding detection》,专门讲解了编码检测的算法,同时使用了三种检测方式(Coding Scheme Method、Character Distribution Method和Two-Char Sequence Distribution Method),其中Character Distribution Method基于统计语言原理。这篇论文的第一作者是一个中国人,一说其猜测准确性远高于同期IE(由于时代已经比较久远,2002年左右,因此未进行深入验证),有较多的人推荐:
https://www-archive.mozilla.org/projects/intl/chardet.html
https://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html
Mozilla浏览器除了使用HTTP头的content-type和网页的的charset外,主要是为了应对charset缺失的情况而需要用到编码检测。在Python世界中,有一个工程基于《A composite approach to language/encoding detection》进行了更多的扩展,支持更多类型的编码方式,链接如下:
https://www.complang.tuwien.ac.at/doc/python-chardet/how-it-works.html
https://github.com/PyYoshi/uchardet
一说,universialchardet较chardet更新且准确率更高,其默认都是使用C++实现的,在Java世界中分别对应juniversalchardet和jchardet两个移植的实现。
2.2 使用juniversalchardet进行编码检测
juniversalchardet最早位于GoogleCode中(http://code.google.com/p/juniversalchardet),上述链接现已失效,可以参考如下两个GitHub的链接:
https://github.com/thkoch2001/juniversalchardet
https://github.com/albfernandez/juniversalchardet
其中,第一个链接的Star数更多,ReadMe中据称是从Google Code中直接迁移过来的,其中还包括了原始的C++代码;第二个链接对Google Code中的原始代码进行了部分优化改写(主要是Latin1Prober、MBCSGroupProber、SBCSGroupProber中的遍历逻辑等,还增加了一个ThaiModel,但不支持GB18030)。由于历史久远,我们已经无从具体考证这两个链接的关系,如有必要建议直接对比代码(下图中的笔者进行过修改,仅供参考):
GitHub上两个juniversalchardet的对比

GitHub上两个juniversalchardet的对比

juniversalchardet支持如下的编码,具体所有的编码可以查看org.mozilla.universalchardet.Constants:
– Chinese
– ISO-2022-CN
– BIG-5
– EUC-TW
– GB18030
– HZ-GB-2312– Cyrillic
– ISO-8859-5
– KOI8-R
– WINDOWS-1251
– MACCYRILLIC
– IBM866
– IBM855– Greek
– ISO-8859-7
– WINDOWS-1253– Hebrew
– ISO-8859-8
– WINDOWS-1255– Japanese
– ISO-2022-JP
– Shift_JIS
– EUC-JP– Korean
– ISO-2022-KR
– EUC-KR

– Unicode
– UTF-8
– UTF-16BE / UTF-16LE
– UTF-32BE / UTF-32LE / X-ISO-10646-UCS-4-3412 / X-ISO-10646-UCS-4-2143

– Others
– WINDOWS-1252

juniversalchardet的使用较为简单,形如:
import org.mozilla.universalchardet.CharsetListener;
import org.mozilla.universalchardet.UniversalDetector;public class TestDetector
{
public static void main(String[] args) throws java.io.IOException
{
// byte[] buf = new byte[] { -78, -35, -63, -15, -55, -25, -123, 94 };
byte[] buf = new byte[] { 49, 48, 50, 52, -78, -35, -63, -15, -55, -25, -123, 94, 32, 116, 54, 54, 121, 46, 99, 111, 109, 46, 106, 112, 103 };UniversalDetector detector = new UniversalDetector(new CharsetListener()
{
@Override
public void report(String charset)
{
System.out.println(charset);
}
});

detector.handleData(buf, 0, buf.length);

detector.dataEnd();

String encoding = detector.getDetectedCharset();
if (encoding != null)
{
System.out.println(“Detected encoding = ” + encoding);
}
else
{
System.out.println(“No encoding detected.”);
}

detector.reset();
}
}

请注意,此前有部分资料中称juniversalchardet在GB18030Prober类和BIG5Prober类中存在BUG:
据称juniversalchardet存在BUG

据称juniversalchardet存在BUG

我们确实可以在GitHub的commit记录中确认该问题(通过代码看更加明显,GB18030Prober和BIG5Prober继承的抽象父类CharDistributionAnalysis中的handleData方法压根就是空的):
https://github.com/thkoch2001/juniversalchardet/commit/3fd330c443272699cd8ba5d7da7e56c27a567ec1
但是,即便如此,在使用juniversalchardet检测本文开头字节数组的编码时仍然会被误判为ISO-8859-7(实际应为GB18030)这与juniversalchardet/Mozilla Universal Charset Detection主要适用于篇幅较长的文本环境有关,在本站的《解决juniversalchardet在处理短文本内容时结果错误的BUG》一文中专门针对juniversalchardet的不足进行了说明:
juniversalchardet识别中文编码错误

juniversalchardet识别中文编码错误

2.3 使用jchardet进行编码检测
jchardet是对chardet的一个Java实现,其原始链接位于:
http://jchardet.sourceforge.net/
在juniversalchardet的ReadMe中,有对jchardet的如下定位:
jchardet is another Java port of the Mozilla’s encoding dectection library. The main difference between jchardet and juniversalchardet is modules they are based on. jchardet is based on the “chardet” module that has long existed. juniversalchardet is based on the “universalchardet” module that is new and generally provides better accuracy on detection results.
据称由于jchardet是基于Mozilla的老版本的编码猜测模块chardet实现的,所以其准确率比较低,大致和IE的接口成功率差不多。jchardet的使用方式较为简单:

package org.mozilla.intl.chardet.test;

import org.mozilla.intl.chardet.nsDetector;
import org.mozilla.intl.chardet.nsICharsetDetectionObserver;

public class Test
{
public static void main(String[] args)
{
byte[] buf = new byte[] { 49, 48, 50, 52, -78, -35, -63, -15, -55, -25, -123, 94, 32, 116, 54, 54, 121, 46, 99, 111, 109, 46, 106, 112, 103 };

nsDetector detector = new nsDetector();
detector.Init(new nsICharsetDetectionObserver()
{
public void Notify(String charset)
{
System.out.println(charset);
}
});

Object done = detector.DoIt(buf, buf.length, false);
detector.DataEnd();
detector.Reset();
System.out.println(done);
}
}

另外,jchardet在处理本文开头、juniversalchardet检测失败的文本内容编码时输出的结果是正确的:
jchardet能够正确识别juniversalchardet识别错误的编码

jchardet能够正确识别juniversalchardet识别错误的编码

2.4 .Net移植的nchardet和nuniversalchardet
.Net中也有两个工程对应chardet和universialchardet,名为nchardet和nuniversalchardet,使用C#实现,据说消除了juniversalchardet在检测BIG5和GB18030时的BUG,没有进行验证。其地址分别为:
https://github.com/thinksea/NChardet(作者是中国人)
https://github.com/hollisterde/nuniversalchardet(http://code.google.com/p/nuniversalchardet,已废弃)

转载时请保留出处,违法转载追究到底:进城务工人员小梅 » 使用Java猜测或检测文本编码(Encoding detection),基于juniversalchardet和jchardet方案

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址