APP下载

关于软件对字符编码方式误判的研究

2014-09-26王玉张永胜

软件工程 2014年9期
关键词:字符集乱码编码方式

王玉++张永胜

摘 要:针对目前字符编码方式众多的现状,应用软件如何更好的判断文件编码属于何种字符集,并将其正确的解码成为不容忽视的问题。针对Windows记事本不能正常显示“联通”二字的Bug进行分析,利用Winhex软件解析文件获得16进制编码,根据得到的编码分析误判原因,通过注释记事本IsTextUTF8函数对分析得到的误判原因进行证实,进一步找到了更多Windows记事本无法正常显示的汉字。

关键词:编码方式;字符集;UTF-8;记事本;误判

中图分类号:TP391.1 文献标识码:A

1 引言(Introduction)

在Windows操作系统环境下,新建一个记事本文档,输入“联通”二字,保存并退出,再次打开该文件时,会发现“联通”二字显示为乱码。该现象普遍存在于中文WIN2000、2000 Pro、2000Server、XP、WIN7、WIN8、WIN8.1等系统中,这是Windows记事本自身存在的一个Bug,至今仍未解决。到底是什么原因导致了该现象的存在呢?

这是计算机编码方式问题导致的。Windows记事本保存文件有4种编码类型,分别为ANSI、Unicode、Unicode big endian、UTF-8,默认保存编码类型为ANSI。输入“联通”二字后,记事本通过ANSI编码方式对其进行存储,再次打开该文件时,记事本错误的判断该文件是由UTF-8进行编码的,便通过对应的解码方式进行解码(Decoding)。由于ANSI和UTF-8编码方式和所属字符集的差异,最终呈现出人们看到的乱码。本文针对该问题进行研究。

2 各种编码方式特点及其发展(Characteristics and

development of various encoding)

2.1 关于ASCII码与扩展字符集

ASCII码是在1947年由美国国家标准学会(American National Standard Institute,ANSI)制定的美国标准信息交换代码(American Standard Code for Information Interchange,ASCII),使用7个二进制位(占一个字节,首位补0),共128种组合表示所有大小写字母[1]。包括数字0到9,标点符号以及在美式英语中使用的特殊控制符号,属单字节字符编码系统。

ASCII码无法表示一些欧洲国家的常用字符,如英镑符号(£)。因此,1981年在标准ASCII码的基础上,使用没有用到的最高码位,共8个二进制位,扩展了128到255(0x80-0xff)共128种状态的字符,称为扩展字符集。

2.2 关于GB2312、GBK、GB18030编码

标准ASCII码和扩展字符集仍然不能满足世界各地的需要,所以不同国家制定了自己特有的字符集。如中国,以ASCII码为基础制定了GB2312编码集。GB2312规定小于127的沿用ASCII码,当两个大于127的字符连在一起时表示一个汉字,这样组合出6763个简体汉字、682个符号[2]。同时将数字、标点、字母都重新编排了占两个字节的编码,这就是半角全角的问题。1995年又以GB2312为基础扩展制定了GBK标准。GBK沿用GB2312的标准,但要求连在一起的两个字符只要第一个大于127就表示一个汉字。为了方便少数民族,后又制定了GB18030编码。

从ASCII、GB2312到GBK再到GB18030,都是向下兼用的,即同一个字符在这些方案中总有相同的编码,后来的标准支持更多的字符,从ANSI派生出的字符集都称为ANSI字符集。

2.3 关于Unicode编码

世界各地不同语言都制定了自己的字符集,种类繁多,国际交流中需要转换字符集极其不便[3]。因此,国际标准化组织(International Organization for Standardization,ISO)开展了ISO/IEC 10646项目,全称“Universal Multiple-Octet Coded Character Set”,简称UCS(双字节称为UCS-2,四字节称为UCS-4),俗称Unicode字符集。Unicode字符集使用16bits表示一个字符,0—127的ASCII码也扩展为两个字节,用0-0x10FFFF来映射这些字符,可表示65536个字符,几乎将世界上所有语言的常用字符收录其中。标准的Unicode编码称为UTF-16,为了能使双字节的Unicode在单字节处理的系统上正确传输,便使用类似MBCS的方式对Unicode进行编码,利用保留字符制定了三套编码方式,分别为UTF-8、UTF-16、UTF-32。

2.4 关于UTF-8编码

UTF-8是用1到4个字节编码Unicode的可变长度字符编码,又称万国码。UTF-8用一个字节来表示字母和一些键盘上的符号,当用多字节表示时UTF-8设定了标志位。

当要表示的内容是7位的时候就用一个字节存储,形如0xxxxxxx。首位0为标志位,剩下码位正好可以表示ASCII码0—127的内容。当要表示的内容在8到11位的时候就用两个字节存储,形如110xxxxx 10xxxxxx。第一个字节的110和第二个字节的10为标志位。当要表示的内容在12到16位的时候就用三个字节存储,形如1110xxxx 10xxxxxx 10xxxxxx。第一个字节的1110和第二、三个字节的10为标志位,其余的码位用来表示汉字。

UTF-8以8位为单元对UCS-2编码模板,详见表1。

表1 UTF-8对UCS-2编码模板

Tab.1 UTF-8 to UCS-2 encoding templateendprint

UCS-2编码值(H) UTF-8编码结构(B)

0000-007F 0xxxxxxx

0080-07FF 110xxxxx 10xxxxxx

0800-FFFF 1110xxxx 10xxxxxx 10xxxxxx

UTF-8以8位为单元对UCS-4编码模板,详见表2。

表2 UTF-8对UCS-4编码模板

Tab.2 UTF-8 to UCS-4 encoding template

UCS-4编码值(H) UTF-8编码结构(B)

0000 0000-0000 007F 0xxxxxxx

0000 0080-0000 07FF 110xxxxx 10xxxxxx

0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

0400 0000-7FFF FFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

2.5 关于BOM问题

新建空文本文档分别以ANSI、Unicode、Unicode big endian、UTF-8编码格式存储,通过软件Winhex解析文件,发现除ANSI编码外,其他编码方式都存在标志头字符,即根据不同的编码方式在其文件头添加规定的标志字符用以区分。Unicode规范中称该标志字符为字节顺序标志(Byter Order Mark,BOM),俗称标签。Winhex解析各种编码类型的BOM,详见表3。

表3 各种编码类型BOM

Tab.3 BOM of various encoding types

编码方式 BOM

ANSI 无

UTF-16(Unicode)/UCS-2 little endian FF FE

UTF-16(Unicode)/UCS-2 big endian FE FF

UTF-8 EF BB BF

3 软件对文件编码方式误判的研究(Research

software for document encoding misjudgment)

在上述编码知识的基础上,下面针对记事本不能正常显示“联通”二字进行分析。

3.1 记事本不能正常显示“联通”二字的研究

新建文本文档1,输入“联通”二字,保存后退出,记事本是以默认存储的编码方式(即ANSI)进行保存的。再次打开后会发现显示为乱码,点击文件-另存为,发现该文件编码方式显示为UTF-8,说明记事本认为这是一个UTF-8编码的文件。如果再新建一个文本文档2,文件-打开,选中文本文档1后,编码方式选择ANSI,打开后便能正确显示“联通”二字,或者在新建文本文档1中输入“联通”二字后,另存时编码选择为UTF-8,保存后退出,再次打开也能正常显示“联通”二字。由此可以推断这是编码问题导致的。

新建文本文档,输入“联通”二字保存后,将文件导入Winhex查看其十六进制编码,发现为C1 AA CD A8(H),由此可以确定记事本是将“联通”二字按照ANSI编码方式进行存储的,因此再次打开时选择ANSI编码,记事本将按照对应方式解码,“联通”二字便可以正常显示了。因此,若首次将“联通”二字保存为UTF-8编码方式,记事本按照UTF-8解码(Decoding)时便能正确显示了。

双击打开文件,内容显示为乱码,是因为记事本将ANSI编码存储的文件用UTF-8编码对应的解码方式进行了解码,致使错误产生。“联通”二字的ANSI编码为C1 AA CD A8,如图1所示,对应二进制代码为1100 0001(C1),1010 1010(AA),1100 1101(CD),1010 1000(A8)。

图1 “联通”的ANSI编码

Fig.1 ANSI encoding of Unicom

对于“联”字,第一二个字节、第三四个字节的起始部分都是“110”和“10”,正好符合UTF-8规则里的双字节模板。再次打开文件时,记事本其实根据二进制代码错误判断得出这是一个UTF-8编码的文件,根据UTF-8规则去掉第一个字节的110和第二个字节的10,得到有效代码为1101010,补上前导0后为0000 0000,0110 1010,即006A(H)在Unicode字符集中对应小写字母j。同理,“通”字也按此解码得到0368(H),在Unicode字符集中该编码不对应任何字符,因此致使记事本显示为乱码。

3.2 对记事本IsTextUTF8函数的研究

那么记事本究竟通过何种方式来判断文件的编码方式呢?

猜测是根据有无BOM判断的。早期一些软件并不在文件头添加相应的BOM文件,因此软件不能单纯通过BOM进行判断,不得不进行模糊判断。通过分析记事本的源代码,可知记事本是通过nputf.c文件中的函数INT IsTextUTF8(LPSTR lpstrInputStream,INT iLen)进行判断的,对此函数作出如下分析。

//基于UTF-8双字节模板编码方式进行判断

//若为UTF-8编码该函数返回真,若判断只属于前128个字符返回假,认为是ASCII码endprint

INT IsTextUTF8(LPSTR lpstrInputStream, INT iLen){

DWORD cOctets; //cOctets若是UTF-8,控制以10开头位段数

UCHAR chr;//一个字节长度的字符类型chr

BOOL bAllAscii= TRUE;//若为真将按照ASCII码标准进行编码

INT i; cOctets= 0;

for(i=0; i < iLen; i++) {

chr= *(lpstrInputStream+i);

if((chr&0x80) != 0) bAllAscii= FALSE;//0x80对应二进制是1000 0000,因为ASCII码只用了7位,首位为零,若是ASCII码,该判断值为假

if(cOctets == 0 {//当字符不全由ACSII组成时

if(chr >= 0x80){//若大于0x80,说明在ASCII码之外

do {

chr <<= 1;//向左移位,用以判定是否符合UTF-8开头110 1110…的方式

cOctets++;//10开头位段数加一

}

while((chr&0x80) != 0);

cOctets--;//临界处理

if(cOctets == 0) return FALSE;//若不符合形式11xx xxxx返回假

}

}

else {

if((chr&0xC0) != 0x80){//若八位组编码符合10xx xxxx形式返回真,否则返回假

return FALSE;

}

cOctets--;//处理下一个八位组编码

}

}//文本统计判断结束

if(cOctets >0) {

return FALSE;//若后边的八位组与首个八位组首位1的个数不满足UTF-8的规律返回假

}

if(bAllAscii) {return FALSE;//若全部满足符合ASCII码,均小于128,返回假

}

return TRUE;//若不满足上述情况返回真

}

由此可以看出,记事本正是通过UTF-8标志位的特殊形式进行判断的,证实了“联通”二字不能正常显示原因的推断。由此,可以得出结论“联通”二字不能正常显示是因为误将ANSI编码方式判断为UTF-8编码,致使解码错误导致无法在相应字符集找到字符,故显示为乱码。

4 更多不能被正确解码的汉字(More characters

can not be correctly decoded)

综上所述,是否符合研究得到形式的字符都会被错误解码呢?

根据误判原因和UTF-8双字节模板特点,发现16进制形式汉字字符,只要第一个字符为C或D,第三个字符为A或B的,都将被误解码。

Code 0 1 2 3 4 5 6 7 8 9 A B C D E F

C0A0馈 愧 溃 坤 昆 捆 困 括 扩 廓 阔 垃 拉 喇 蜡

C0B0腊 辣 啦 莱 来 赖 蓝 婪 栏 拦 篮 阑 兰 澜 谰 揽

C1A0痢 立 粒 沥 隶 力 璃 哩 俩 联 莲 连 镰 廉 怜

C1B0涟 帘 敛 脸 链 恋 炼 练 粮 凉 梁 粱 良 两 辆 量

……

D0A0小 孝 校 肖 啸 笑 效 楔 些 歇 蝎 鞋 协 挟 携

D0B0邪 斜 胁 谐 写 械 卸 蟹 懈 泄 泻 谢 屑 薪 芯 锌

……

符合上述特点的汉字组成的高频词语,如“笑脸”“谢谢”“两辆”“拉力”等,它们不能正确的显示会使人们产生困惑。在这些汉字之后继续输入其他汉字即可正常显示,而全文仅由符合上述特点的汉字组成便会显示为乱码。这是由于INT IsTextUTF8(LPSTR lpstrInputStream,INT iLen)函数是基于统计判断的,一篇文章只要有一组不符合UTF-8编码形式软件便会认为文本是ANSI编码。

5 结论(Conclusion)

本文通过分析发现,汉字文本显示为乱码是软件对文件编码方式的错误判断造成的,发现了更多记事本不能正确显示的汉字,由这些汉字组成的高频词语不能正确的显示会使人们产生困惑。对软件如何能正确判断文件的编码方式,还需要具体的程序来实现。

参考文献(References)

[1] 冯灵清,杨怀卿,刘宇晶.常用编码方式及其转换格式[J].计算

机时代,2012(1):33-35.

[2] 张晓培,李祥.从Unicode到GBK的内码转换[J].微计算机应用,

2006,27(6):757-759.

[3] 邱发林,李伟,周绍景.Unicode及中文到Unicode转换[J].科技

信息,2006(3):21-22.

作者简介:

王 玉(1993-),男,本科.研究领域:计算机应用.

张永胜(1962-),男,教授,硕士生导师.研究领域:面向服务

计算,Web Services安全.endprint

INT IsTextUTF8(LPSTR lpstrInputStream, INT iLen){

DWORD cOctets; //cOctets若是UTF-8,控制以10开头位段数

UCHAR chr;//一个字节长度的字符类型chr

BOOL bAllAscii= TRUE;//若为真将按照ASCII码标准进行编码

INT i; cOctets= 0;

for(i=0; i < iLen; i++) {

chr= *(lpstrInputStream+i);

if((chr&0x80) != 0) bAllAscii= FALSE;//0x80对应二进制是1000 0000,因为ASCII码只用了7位,首位为零,若是ASCII码,该判断值为假

if(cOctets == 0 {//当字符不全由ACSII组成时

if(chr >= 0x80){//若大于0x80,说明在ASCII码之外

do {

chr <<= 1;//向左移位,用以判定是否符合UTF-8开头110 1110…的方式

cOctets++;//10开头位段数加一

}

while((chr&0x80) != 0);

cOctets--;//临界处理

if(cOctets == 0) return FALSE;//若不符合形式11xx xxxx返回假

}

}

else {

if((chr&0xC0) != 0x80){//若八位组编码符合10xx xxxx形式返回真,否则返回假

return FALSE;

}

cOctets--;//处理下一个八位组编码

}

}//文本统计判断结束

if(cOctets >0) {

return FALSE;//若后边的八位组与首个八位组首位1的个数不满足UTF-8的规律返回假

}

if(bAllAscii) {return FALSE;//若全部满足符合ASCII码,均小于128,返回假

}

return TRUE;//若不满足上述情况返回真

}

由此可以看出,记事本正是通过UTF-8标志位的特殊形式进行判断的,证实了“联通”二字不能正常显示原因的推断。由此,可以得出结论“联通”二字不能正常显示是因为误将ANSI编码方式判断为UTF-8编码,致使解码错误导致无法在相应字符集找到字符,故显示为乱码。

4 更多不能被正确解码的汉字(More characters

can not be correctly decoded)

综上所述,是否符合研究得到形式的字符都会被错误解码呢?

根据误判原因和UTF-8双字节模板特点,发现16进制形式汉字字符,只要第一个字符为C或D,第三个字符为A或B的,都将被误解码。

Code 0 1 2 3 4 5 6 7 8 9 A B C D E F

C0A0馈 愧 溃 坤 昆 捆 困 括 扩 廓 阔 垃 拉 喇 蜡

C0B0腊 辣 啦 莱 来 赖 蓝 婪 栏 拦 篮 阑 兰 澜 谰 揽

C1A0痢 立 粒 沥 隶 力 璃 哩 俩 联 莲 连 镰 廉 怜

C1B0涟 帘 敛 脸 链 恋 炼 练 粮 凉 梁 粱 良 两 辆 量

……

D0A0小 孝 校 肖 啸 笑 效 楔 些 歇 蝎 鞋 协 挟 携

D0B0邪 斜 胁 谐 写 械 卸 蟹 懈 泄 泻 谢 屑 薪 芯 锌

……

符合上述特点的汉字组成的高频词语,如“笑脸”“谢谢”“两辆”“拉力”等,它们不能正确的显示会使人们产生困惑。在这些汉字之后继续输入其他汉字即可正常显示,而全文仅由符合上述特点的汉字组成便会显示为乱码。这是由于INT IsTextUTF8(LPSTR lpstrInputStream,INT iLen)函数是基于统计判断的,一篇文章只要有一组不符合UTF-8编码形式软件便会认为文本是ANSI编码。

5 结论(Conclusion)

本文通过分析发现,汉字文本显示为乱码是软件对文件编码方式的错误判断造成的,发现了更多记事本不能正确显示的汉字,由这些汉字组成的高频词语不能正确的显示会使人们产生困惑。对软件如何能正确判断文件的编码方式,还需要具体的程序来实现。

参考文献(References)

[1] 冯灵清,杨怀卿,刘宇晶.常用编码方式及其转换格式[J].计算

机时代,2012(1):33-35.

[2] 张晓培,李祥.从Unicode到GBK的内码转换[J].微计算机应用,

2006,27(6):757-759.

[3] 邱发林,李伟,周绍景.Unicode及中文到Unicode转换[J].科技

信息,2006(3):21-22.

作者简介:

王 玉(1993-),男,本科.研究领域:计算机应用.

张永胜(1962-),男,教授,硕士生导师.研究领域:面向服务

计算,Web Services安全.endprint

INT IsTextUTF8(LPSTR lpstrInputStream, INT iLen){

DWORD cOctets; //cOctets若是UTF-8,控制以10开头位段数

UCHAR chr;//一个字节长度的字符类型chr

BOOL bAllAscii= TRUE;//若为真将按照ASCII码标准进行编码

INT i; cOctets= 0;

for(i=0; i < iLen; i++) {

chr= *(lpstrInputStream+i);

if((chr&0x80) != 0) bAllAscii= FALSE;//0x80对应二进制是1000 0000,因为ASCII码只用了7位,首位为零,若是ASCII码,该判断值为假

if(cOctets == 0 {//当字符不全由ACSII组成时

if(chr >= 0x80){//若大于0x80,说明在ASCII码之外

do {

chr <<= 1;//向左移位,用以判定是否符合UTF-8开头110 1110…的方式

cOctets++;//10开头位段数加一

}

while((chr&0x80) != 0);

cOctets--;//临界处理

if(cOctets == 0) return FALSE;//若不符合形式11xx xxxx返回假

}

}

else {

if((chr&0xC0) != 0x80){//若八位组编码符合10xx xxxx形式返回真,否则返回假

return FALSE;

}

cOctets--;//处理下一个八位组编码

}

}//文本统计判断结束

if(cOctets >0) {

return FALSE;//若后边的八位组与首个八位组首位1的个数不满足UTF-8的规律返回假

}

if(bAllAscii) {return FALSE;//若全部满足符合ASCII码,均小于128,返回假

}

return TRUE;//若不满足上述情况返回真

}

由此可以看出,记事本正是通过UTF-8标志位的特殊形式进行判断的,证实了“联通”二字不能正常显示原因的推断。由此,可以得出结论“联通”二字不能正常显示是因为误将ANSI编码方式判断为UTF-8编码,致使解码错误导致无法在相应字符集找到字符,故显示为乱码。

4 更多不能被正确解码的汉字(More characters

can not be correctly decoded)

综上所述,是否符合研究得到形式的字符都会被错误解码呢?

根据误判原因和UTF-8双字节模板特点,发现16进制形式汉字字符,只要第一个字符为C或D,第三个字符为A或B的,都将被误解码。

Code 0 1 2 3 4 5 6 7 8 9 A B C D E F

C0A0馈 愧 溃 坤 昆 捆 困 括 扩 廓 阔 垃 拉 喇 蜡

C0B0腊 辣 啦 莱 来 赖 蓝 婪 栏 拦 篮 阑 兰 澜 谰 揽

C1A0痢 立 粒 沥 隶 力 璃 哩 俩 联 莲 连 镰 廉 怜

C1B0涟 帘 敛 脸 链 恋 炼 练 粮 凉 梁 粱 良 两 辆 量

……

D0A0小 孝 校 肖 啸 笑 效 楔 些 歇 蝎 鞋 协 挟 携

D0B0邪 斜 胁 谐 写 械 卸 蟹 懈 泄 泻 谢 屑 薪 芯 锌

……

符合上述特点的汉字组成的高频词语,如“笑脸”“谢谢”“两辆”“拉力”等,它们不能正确的显示会使人们产生困惑。在这些汉字之后继续输入其他汉字即可正常显示,而全文仅由符合上述特点的汉字组成便会显示为乱码。这是由于INT IsTextUTF8(LPSTR lpstrInputStream,INT iLen)函数是基于统计判断的,一篇文章只要有一组不符合UTF-8编码形式软件便会认为文本是ANSI编码。

5 结论(Conclusion)

本文通过分析发现,汉字文本显示为乱码是软件对文件编码方式的错误判断造成的,发现了更多记事本不能正确显示的汉字,由这些汉字组成的高频词语不能正确的显示会使人们产生困惑。对软件如何能正确判断文件的编码方式,还需要具体的程序来实现。

参考文献(References)

[1] 冯灵清,杨怀卿,刘宇晶.常用编码方式及其转换格式[J].计算

机时代,2012(1):33-35.

[2] 张晓培,李祥.从Unicode到GBK的内码转换[J].微计算机应用,

2006,27(6):757-759.

[3] 邱发林,李伟,周绍景.Unicode及中文到Unicode转换[J].科技

信息,2006(3):21-22.

作者简介:

王 玉(1993-),男,本科.研究领域:计算机应用.

张永胜(1962-),男,教授,硕士生导师.研究领域:面向服务

计算,Web Services安全.endprint

猜你喜欢

字符集乱码编码方式
对症下药解决多种乱码难题
这些真的不是乱码,是汉字
MySQL数据库字符集的问题研究
ORACLE字符集问题的分析
GCOA算法
ORACLE数据库字符集问题及解决方法
可穿戴式多通道传感系统功能需求分析及设计
医院信息系统Oracle数据库中导入数据中文乱码的解决技术
混合编码方式自适应差分进化算法优化设计宽带天线
炫迈:用神奇乱码勾引你视线