GBK、Shift-JIS、BIG5编码检测算法

GBK、Shift-JIS、BIG5编码检测算法

字符串的编码检测需要使用自定义的映射表,使用系统自带的Codepage是不大可能有准确率的,系统Codepage会将它所有没定义的字符映射为空格。
GBK、Shift-JIS、BIG5的码表空间都是不连贯的,而它们的有效空间也不完全重合,这为检测编码类型提供了可能性。

检测算法:
1、建立字符映射表:将任一ANSI编码的所有字符全映射,从0×00到0xFFFF都有Unicode字符对应,但需要注意的是没有定义的字符统统映射到Unicode的0xFFFD(共三个映射表,既可用于检测也可用于转换)。
2、预设字符串的编码是Shift-JIS。
3、使用Shift-JIS的映射表从字符串第一个字符开始检测直至最后一个字符。如果遇到有字符映射到0xFFFD,设置预设编码是GBK,立刻停止步骤3,跳至步骤4。
4、如果预设编码是GBK,使用GBK的映射表从字符串第一个字符开始检测直至最后一个字符。如果遇到有字符映射到0xFFFD,设置预设编码是BIG5,立刻停止步 骤4,跳至步骤5。
5、如果预设编码是BIG5,使用BIG5的映射表从字符串第一个字符开始检测直至最后一个字符。如果遇到有字符映射到0xFFFD,设置预设编码是未知编码,立刻停止步 骤5,跳至步骤6。
6、返回预设编码。

这个算法的编码检测优先度是Shift-JIS>GBK>BIG5,也即如果顺利通过当前检测,则跳过后面所有检测。事实上,有大量字符串是能通过所有检测的。例如只有一个字符的字符串,假设这个字符是0×8140,在三个编码当中,都不会映射到Unicode的0xFFFD,因此能通过所有检测。但这没意义了。设定了优先度后是为了告诉用户最可能的一种编码。

为什么设定Shift-JIS>GBK>BIG5?
Shift-JIS的码表空间是0×00-0x7F、0xA1-0xDF、0×8140-0xFC4B
GBK的码表空间是0×00-0x7F、0×8140-0xFEFE
BIG5的码表空间是0×00-0x7F、0×8140-0xFEFE
但双字节段(0×8140以上)都不是全部已定义,Shift-JIS在0×8140以上的有效字符数是7724,GBK是21885,BIG5是19782。
GBK的覆盖面最大,有效空间基本覆盖了Shift-JIS,因此一个字符串如果能通过Shift-JIS检测,也差不多能通过GBK检测。如果将GBK的优先度设得比Shift-JIS高,那么大量真正是Shift-JIS编码的字符串就压根没机会返回给用户了。从反方向看,GBK中存在数量庞大的字符Shift-JIS没定义,Shift-JIS是高度覆盖不住GBK的,一个GBK文本从概率上没那么容易检测成Shift-JIS。也即:如果一个文本的真正编码是Shift-JIS,那么优先使用Shift-JIS检测自然不会有问题;如果它是GBK,那么优先使用Shift-JIS检测也不大会返回Shift-JIS。因此Shift-JIS应当优先于GBK。
Shift-JIS和BIG5的关系的考虑也类似。

从转换日系音乐cue、日文小说的宅用途出发,也应当将Shift-JIS设置为最高。

下面三张图是是Shift-JIS编码的小说“文学少女”と死にたがりの道化的转换结果。左边是当作本地编码的处理结果,可以无视。右边才是转换结果。
(程序和文本下载:http://code.google.com/p/unicue/downloads/detail?name=Ansi2Unicode_1.01.zip

使用Shift-JIS映射表转换,结果自然是正确的。

强行使用GBK映射表转换,没有出现标记0xFFFD(0xFFFD:�),也即能通过GBK检测

强行使用Big5转换,出现0xFFFD标记,也即通不过BIG5检测。BIG5跟Shift-JIS的有效空间重合度没那么高,区分相对容易一点

另外一个GBK文本强行使用Shift-JIS转换的结果。很容易就出现了0xFFFD标记

那么GBK和BIG5的优先度应该谁高呢?这里就见仁见智了。GBK的字符数比BIG5多,从概率上GBK相对容易覆盖住BIG5,BIG5相对不容易覆盖住GBK。倘若采用Shift-JIS和GBK之间的比较方法,应该是BIG5的优先度比GBK高。但从实际情况来看,真正BIG5编码的文本强行使用GBK映射表转换比较容易出现0xFFFD标记,真正GBK编码的文本强行使用BIG5映射表转换反而不容易出现0xFFFD标记。

GBK文本强行使用BIG5映射表转换的结果,不容易出现0xFFFD标记

BIG5文本使用BIG5映射表转换,正常的结果

BIG5文本强行使用GBK映射表转换,很容易出现0xFFFD标记

现实结果和表面现象完全相反。如果一个文本的真正编码是GBK,那么优先使用GBK检测自然不会有问题;如果它是BIG5,那么优先使用GBK检测也不大会返回GBK。因此余倾向于GBK的优先度高于BIG5。

最后附上源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#define CODETYPE_DEFAULT  0
#define CODETYPE_GBK      1
#define CODETYPE_BIG5     2
#define CODETYPE_SHIFTJIS 3
#define CODETYPE_UTF8     4
#define CODETYPE_UNICODE  5
 
#define GBKOFFSET         0x8140
#define BIG5OFFSET        0x8140
#define JIS0201OFFSET     0xA1
#define JIS0201LENGTH     63
#define JIS0208OFFSET     0x8140
 
typedef int CodeType;
 
CodeType CheckCodeType( const char * AnsiStr, UINT length)
{
     if (!AnsiStr)
         return CODETYPE_DEFAULT;
     if (length==0)
         return CODETYPE_DEFAULT;
 
     //判断是否是UTF-8编码
     UINT i=0;
     BOOL isUTF8=TRUE;
     while (i<length)
     {
     ......
     }
 
     if (isUTF8)
         return CODETYPE_UTF8;
 
     UINT JISMapLength,GBKMapLength,BIG5MapLength;
     char *JISMapBuffer,*GBKMapBuffer,*BIG5MapBuffer;
     //加载映射表
     TCHAR path[MAX_PATH]; //最长260
     GetModuleFileName(NULL, path, MAX_PATH);
     CString mapPath=CString(path);
     int position=mapPath.ReverseFind( '\\' );
     mapPath=mapPath.Left(position);
     CString mapFolder=mapPath;
     CFile loadMap;
 
     mapPath=mapFolder+_T( "\\jis2u-little-endian.map" );
     if (!loadMap.Open(mapPath,CFile::modeRead))
     {
         loadMap.Close();
         ::AfxMessageBox(_T( "JIStoUnicode map loading error!" ));
         return CODETYPE_DEFAULT;
     }
     JISMapLength=loadMap.GetLength();
     JISMapBuffer= new char [JISMapLength];
     loadMap.Read(( void *)JISMapBuffer,JISMapLength);
     loadMap.Close();
 
     mapPath=mapFolder+_T( "\\gb2u-little-endian.map" );
     if (!loadMap.Open(mapPath,CFile::modeRead))
     {
         loadMap.Close();
         ::AfxMessageBox(_T( "GBKtoUnicode map loading error!" ));
         if (JISMapBuffer)
             delete []JISMapBuffer;
         return CODETYPE_DEFAULT;
     }
     GBKMapLength=loadMap.GetLength();
     GBKMapBuffer= new char [GBKMapLength];
     loadMap.Read(( void *)GBKMapBuffer,GBKMapLength);
     loadMap.Close();
 
     mapPath=mapFolder+_T( "\\b2u-little-endian.map" );
     if (!loadMap.Open(mapPath,CFile::modeRead))
     {
         loadMap.Close();
         ::AfxMessageBox(_T( "Big5toUnicode map loading error!" ));
         if (JISMapBuffer)
             delete []JISMapBuffer;
         if (GBKMapBuffer)
             delete []GBKMapBuffer;
         return CODETYPE_DEFAULT;
     }
     BIG5MapLength=loadMap.GetLength();
     BIG5MapBuffer= new char [BIG5MapLength];
     loadMap.Read(( void *)BIG5MapBuffer,BIG5MapLength);
     loadMap.Close();
 
     //检测编码,顺序是Shift-JIS>GBK>BIG5
     //如果通过前面编码检测,则跳过后面所有检测
     //检查对应的Unicode字符是否为0xFFFD,文本越长,准确度越高
     //语义分析之类是无理的了
     CodeType strCodeType=CODETYPE_SHIFTJIS;
     unsigned char low=0,high=0;
     WCHAR chr=0;
     for (i=0;i<length;)
     {
         memcpy (&high,AnsiStr+i,1); //读取第一个byte
         i++;
         if (high<=0x7F)  //ASCII码区
         {
             low=high;
             high=0;
         }
         else if ((high>=0xA1)&&(high<=0xDF))  //半角片假名区
         {
             low=high;
             high=0;
         }
         else  //双字节区
         {
             memcpy (&low,AnsiStr+i,1); //读取低位
             i++;
         }
 
         chr=low+high*256;
         if (chr<0x80) // ASCII
         {}
         else if (chr<JIS0201OFFSET) // 0x80 - 0xA0 未定义空间
         {
             strCodeType=CODETYPE_DEFAULT; // 未知编码
             break ;
         }
         else if (chr<(JIS0201OFFSET+JIS0201LENGTH)) // 0xA1 - 0xDF 半角假名区
         {}
         else if (chr<JIS0208OFFSET) // 0xE0 - 0x813F 未定义空间
         {
             strCodeType=CODETYPE_DEFAULT;  // 未知编码
             break ;
         }
         else // 0x8140 - 0xFFFF
         {
             int offset;
             offset=chr-JIS0208OFFSET+JIS0201LENGTH;
             memcpy (( void *)&chr,JISMapBuffer+offset*2,2);
             if (chr==0xFFFD)
             {
                 strCodeType=CODETYPE_GBK;
                 break ;
             }
         }
     }
 
     if (strCodeType==CODETYPE_GBK)
     {
         for (i=0;i<length;)
         {
             memcpy (&high,AnsiStr+i,1); //读取第一个byte
             i++;
             if (high>0x7F) //第一个byte是高位
             {
                 memcpy (&low,AnsiStr+i,1); //读取低位
                 i++;
             }
             else
             {
                 low=high;
                 high=0;
             }
 
             chr=low+high*256;
             if (chr<0x80) // ASCII码
             {}
             else if (chr<GBKOFFSET) // 0x80 - 0x813F 未定义空间
             {
                 strCodeType=CODETYPE_DEFAULT;   // 未知编码
                 break ;
             }
             else
             {
                 int offset;
                 offset=chr-GBKOFFSET;
                 memcpy (( void *)&chr,GBKMapBuffer+offset*2,2);
                 if (chr==0xFFFD)
                 {
                     strCodeType=CODETYPE_BIG5;
                     break ;
                 }
             }
         }
     }
 
     if (strCodeType==CODETYPE_BIG5)
     {
         for (i=0;i<length;)
         {
             memcpy (&high,AnsiStr+i,1); //读取第一个byte
             i++;
             if (high>0x7F) //第一个byte是高位
             {
                 memcpy (&low,AnsiStr+i,1); //读取低位
                 i++;
             }
             else
             {
                 low=high;
                 high=0;
             }
 
             chr=low+high*256;
             if (chr<0x80) // ASCII码
             {}
             else if (chr<BIG5OFFSET) // 0x80 - 0x813F 未定义空间
             {
                 strCodeType=CODETYPE_DEFAULT;   // 未知编码
                 break ;
             }
             else
             {
                 int offset;
                 offset=chr-BIG5OFFSET;
                 memcpy (( void *)&chr,BIG5MapBuffer+offset*2,2);
                 if (chr==0xFFFD)
                 {
                     strCodeType=CODETYPE_DEFAULT;
                     break ;
                 }
             }
         }
     }
 
     if (JISMapBuffer)
         delete []JISMapBuffer;
     JISMapBuffer=NULL;
     if (GBKMapBuffer)
         delete []GBKMapBuffer;
     GBKMapBuffer=NULL;
     if (BIG5MapBuffer)
         delete []BIG5MapBuffer;
     BIG5MapBuffer=NULL;
 
     return strCodeType;
}

映射表文件并没有从0×00持续到0xFFFF,而是采取了精简策略。
GBK和BIG5的映射表范围均是0×8140-0xFFFF(其中未定义的字符都映射到0xFFFD),转换是对0×80-0x813F的字符也都映射到0xFFFD,检测则是立刻退出。Shift-JIS的映射表由两段凑在一起,因此有两个偏移,范围是0xA1-0xDF和0×8140-0xFFFF,转换是对0×80-0xA0和0xE0-0x813F的字符也都映射到0xFFFD,检测是立刻退出。最后的还有一个检测会对0×8140-0xFFFF的字符把关。代码实现和算法略有出入,但基本一致。


http://kuyur.info/blog/archives/635

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值