读取文本文件或者接收字节流时需要搞清字符编码才能正确处理,编码识别错误是出现乱码的主要原因。理解编码识别方法之前建议阅读:常用字符集编码的概要特性(一)和 常用字符集编码的概要特性(二)。
通过约定识别
为了接收字节流时能正确识别编码,很多情况下发送字节流的同时会把字节流对应的编码发送给接收方,这种情况可以理解为发送和接收双方的约定。HTTP协议就有这样的约定,浏览器就是通过约定来识别网页的编码。HTTP协议的响应头会有这样的约定:
Content-Type: text/html;charset=utf-8
如果打开一个本地的Html文件,其中也会有关于字符编码的约定:
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8“/>
邮件客户端程序也是通过上述约定来识别字符的,在邮件的头部有Charset的声明。
UCS-2/UTF-16的BOM头也可以理解为是字符编码的约定,打开文本文件时可以用BOM来识别编码。
靠约定识别编码的情形很多,例如MySQL环境中有一组变量来约定客户端SQL语句的编码以及服务器返回内容的编码。可以用SQL命令来查看这组变量:
show variables like ‘%character_set%’;
如果没有约定就要尝试去猜测字符编码,以下会介绍几种猜测的方法。
通过编码规律识别
UTF-8编码是UCS-2/UCS-4编码按照一定规律转换得到的,UTF-8编码本身有也具有一定规律,这种规律可以作为识别字符编码的依据。下面给出识别一个字符串是否是UTF-8编码的PHP实现:
<?php
function isValidUtf8($string)
{
$str_len = strlen($string);
for($i=0;$i<$str_len;)
{
$str = ord($string[$i]);
if($str>=0 && $str < 0×7f)
{
$i++;
continue;
}
if($str< 0xc0 || $str>0xfd) return false;
$count = $str>0xfc?5:$str>0xf8?4:$str>0xf0?3:$str>0xe0?2:1;
if($i+$count > $str_len) return false;
$i++;
for($m=0;$m<$count;$m++)
{
if(ord($string[$i])<0×80 || ord($string[$i])>0xbf) return false;
$i++;
}
}
return true;
}
?>
用此规律识别UTF-8编码会有一定误差,其他编码也会有某些字符符合UTF-8编码规律的情况。例如GBK编码的“联通”、“学习”等词就符合UTF-8编码的规律。在Windows记事本中只写入“联通”两字后直接保存,重新打开时就会看见两个黑框。原因是记事本错误地把GBK编码的“联通”两字识别为UTF-8编码了,系统字体中没有这两个识别出来的字符,所以只能看见黑框。误把其他编码当作UTF-8编码的例子很多,例如访问URL:http://www.google.cn/search?q=%D1%A7%CF%B0,也会看到错误结果。“%D1%A7%CF%B0”是GBK编码的“学习”,Google把它当作了UTF-8编码。
供识别的字符串越长,出现这种识别错误的概率就越低。
通过编码范围识别
很多字符编码不像UTF-8编码存在明显规律,例如Big5、GBK、GB2312等。识别这些编码可以使用的方法是通过编码范围来识别。例如GB2312的编码范围是0xA1A1-0×7E7E,如果某个字符串中有字节不在此范围内,就可以认为该字符串不是GB2312编码。
通过编码范围识别的方法误差很大,原因是很多双字节编码的编码范围重合度很高,一个字符串同时落在几个编码的编码范围就很难确定。ISO-8859-1编码占用了0×00-0xff内所有空间,所以无法通过编码范围来识别。
基于语义的识别
基于语义识别字符编码的方法的本质是把文本流当作什么编码来理解更符合语义,具体可以根据字频、词频、上下文环境等方式识别。例如某段文本中“B5C4(’的’字的GBK编码)”两个字节出现的频度比较高,则该段文本是GBK编码的可能性就非常大。基于语义的识别准确度取决于供识别的文本流长度和所采用的识别语料。
字符集的识别
字符集编码的识别实际上有两部分,字符编码识别和字符集识别。多数情况下是识别编码,对于通用字符集(UCS)的编码(如UCS-2、UTF-8)有时需要识别字符集。例如在只希望处理中文内容的环境中,对拿到的UTF-8编码的文件就需要识别字符集,如果该文件中所有的字符都是韩文字符,就应该被抛弃。
可用的字符集识别方法很多:
- 通过编码转换来识别。如果把某段文本从UTF-8编码转换到GBK编码时有很多字符不能被转换,就可以认为该文件不是GBK字符集。通常编码转换程序会把不能转换的字符显示为’?'。
- 通过字符编码的范围来判断。CJK汉字在Unicode字符集中占有连续的位置,如果某个UTF-8文件中所有字符都在Unicode定义的CJK汉字区,可以认为该文件是UTF-8编码的中文文件。GBK编码包括俄文字符、日文字符等,这些字符在GBK编码中范围也是连续的。即使某个文件能从UTF-8编码正确转换到GBK编码,也有可能是UTF-8编码的俄文文件。这种情况也可以通过转换后编码的范围来判断。
- 基于语义的识别。