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

解决juniversalchardet在处理短文本内容时结果错误的BUG

摘要:本文指出了Mozilla Universal Charset Detection的一个Java实现(juniversalchardet)在处理短文本时的一个BUG、该BUG产生的原因及解决该BUG的方式。
一、问题现象
在《使用Java猜测或检测文本编码(Encoding detection),基于juniversalchardet和jchardet方案》一文中,指出了使用juniversalchardet检测某些文本编码是存在错误的情况。例如,考虑如下的字节数组(第一个数组是第二个数组的一部分,未加粗的为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 };
直接使用juniversalchardet时检测结果为错误的ISO-8859-7(实际应为GB18030):
juniversalchardet识别中文编码错误

juniversalchardet识别中文编码错误

二、问题分析
该问题的产生原因与与juniversalchardet主要适用于篇幅较长的文本环境有关和基于统计原理有关。
org.mozilla.universalchardet.prober.distributionanalysis.CharDistributionAnalysis抽象类实现了《A composite approach to language/encoding detection》一文中基于统计规律的Character Distribution Method,继承CharDistributionAnalysis的主要有几个类:
CharDistributionAnalysis抽象类的继承层次

CharDistributionAnalysis抽象类的继承层次

该类中的关键之一在于通过handleOneChar方法统计有效字符及常用字符个数:
CharDistributionAnalysis抽象类的handleOneChar方法

CharDistributionAnalysis抽象类的handleOneChar方法

并在需要使用置信率时,通过getConfidence方法计算置信率:

CharDistributionAnalysis的getConfidence方法

CharDistributionAnalysis的getConfidence方法

在handleOneChar方法中,调用了被子类Override的getOrder方法,该方法用于计算一个字符在所属字符集中的“区位码”(实际可以理解为CodePoint,就是一个字符所对应的索引)。有了这个区位码后,再查找对应*****DistributionAnalysis中记录了每一个字符分布频率排名的常量数组,即可判断出某一字符是否为有效的字符及是否为使用频率排序后靠前的512个常用字符。例如GB2312DistributionAnalysis:
GB2312DistributionAnalysis的原理

GB2312DistributionAnalysis的原理

和Big5DistributionAnalysis:
Big5DistributionAnalysis的原理

Big5DistributionAnalysis的原理

对于Big5而言,其中文字符开始于A440,这就是上图中getOrder的计算方法和‘空洞’的来源:
0 1 2 3 4 5 6 7 8 9 a b c d e f
A140
A150 ·
A160
A170 ︿
A180
A190
A1A0
A1B0 §
A1C0 ¯ _ ˍ
A1D0 × ÷ ±
A1E0
A1F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A240
A250 °
A260
A270
A280
A290
A2A0
A2B0
A2C0
A2D0
A2E0
A2F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A340 Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ
A350 Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ
A360 ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ
A370 φ χ ψ ω
A380
A390
A3A0
A3B0 ˙ ˉ ˊ ˇ ˋ
A3C0
A3D0
A3E0
A3F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A440
A450
A460
A470
A480
A490
A4A0
A4B0
A4C0
A4D0 廿
A4E0
A4F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A540
A550
A560
A570
A580
A590
A5A0
A5B0
A5C0
A5D0
A5E0 仿
A5F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A640
A650
A660
A670
A680
A690
A6A0
A6B0
A6C0
A6D0
A6E0 西
A6F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A740
A750
A760
A770
A780
A790
A7A0
A7B0 尿
A7C0
A7D0
A7E0
A7F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A840
A850
A860
A870 禿
A880
A890
A8A0
A8B0
A8C0 使
A8D0
A8E0
A8F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
A940
A950
A960
A970
A980
A990
A9A0
A9B0 彿
A9C0 忿
A9D0
A9E0
A9F0
0 1 2 3 4 5 6 7 8 9 a b c d e f
AA40
AA50 歿
AA60
AA70 沿
AA80
AA90
AAA0
AAB0
AAC0
AAD0
AAE0
AAF0
0 1 2 3 4 5 6 7 8 9 a b c d e f
AB40 便
AB50
AB60
AB70
AB80
AB90
ABA0
ABB0 姿
ABC0
ABD0
ABE0
ABF0
0 1 2 3 4 5 6 7 8 9 a b c d e f
AC40
AC50
AC60
AC70
AC80
AC90
ACA0
ACB0
ACC0
ACD0
ACE0 穿
ACF0 竿 羿
0 1 2 3 4 5 6 7 8 9 a b c d e f
AD40
AD50
AD60
AD70
AD80
AD90
ADA0
ADB0
ADC0 倀
ADD0
ADE0
ADF0
0 1 2 3 4 5 6 7 8 9 a b c d e f
AE40
AE50
AE60
AE70
AE80
AE90
AEA0
AEB0
AEC0
AED0
AEE0
AEF0
0 1 2 3 4 5 6 7 8 9 a b c d e f
AF40
AF50
AF60
AF70
AF80
AF90
AFA0
AFB0
AFC0
AFD0
AFE0
AFF0
显然,在原始代码中有两个常量阈值:
public static final int ENOUGH_DATA_THRESHOLD = 1024;
public static final int MINIMUM_DATA_THRESHOLD = 4;
我们应该注意到,尽管juniversalchardet支持GB18083编码,但判断同时支持繁简体的GB18083统计规律(Character Distribution)的却是使用的仅仅针对简体GB2312统计规律的org.mozilla.universalchardet.prober.distributionanalysis.GB2312DistributionAnalysis。因此,对于‘區’(繁体的‘区’)字而言,GB2312DistributionAnalysis的getOrder方法将直接返回-1(即不认为是有效的GB2312字符)。当输入文本为:
byte[] buf = new byte[] { -78, -35, -63, -15, -55, -25, -123, 94 };
时,通过调试可以证明:
juniversalchardet调试1

juniversalchardet调试1

此时,totalChars=3而不是4(因为‘區’不是有效的GB2312字符), 由于MINIMUM_DATA_THRESHOLD=4未被满足,因此上述输入文本被直接被认为SURE_NO(置信率=0.01),在后续的置信率的比较中也小于ISO-8895-7,导致了最终检测的不正确。
三、问题解决
由于Mozilla本身应对的场景是网页内容的编码检测,因此其可用的文本数量往往较多,这也是之所以ENOUGH_DATA_THRESHOLD被设置为1024的原因。不过,一旦输入本文较短,显然就存在误判的可能了。此时,有三种思路:
①优化GB2312DistributionAnalysis,增加针对GB18030编码的统计学规律(即org.mozilla.universalchardet.prober.distributionanalysis.GB18030DistributionAnalysis)。尽管GB18030的统计学规律没有直接数据,但可以结合BIG5DistributionAnalysis,既可以求出GB18030下的字符分布规律(需要进行BIG5到GB18030的映射,由于GB2312天然兼容,所以可以直接使用;由于GB18030码表较大,可能还需要考虑优化);
②根据输入文本的长度动态地决定MINIMUM_DATA_THRESHOLD;
③直接注释掉MINIMUM_DATA_THRESHOLD的判断,使用
float r = this.freqChars / ((this.totalChars – this.freqChars) * this.typicalDistributionRatio);
 计算置信率。这是一种简单暴力的方法,但在相当程度上是可行的:
解决juniversalchardet在处理短文本时的BUG

解决juniversalchardet在处理短文本时的BUG

转载时请保留出处,违法转载追究到底:进城务工人员小梅 » 解决juniversalchardet在处理短文本内容时结果错误的BUG

分享到:更多 ()

评论 1

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    666,建议给那些库提一些PR呀

    Tesal4年前 (2020-02-04)回复