以下内容是根据网上资料和个人理解整理出来的知识概要,如有错误,欢迎拍砖。
常用编码方式
常见的有四种:UTF-8、GB2312、GBK、GB18030。
先撇开UTF-8不谈,GB2312、GBK、GB18030这三种编码可以说都是为了汉字诞生的,三者的区别,可以简单理解为字符集大小的差异:GB2312 < GBK < GB18030。在这个不断扩充的过程中,少数民族语言、韩文、日文等各种字符也都被收录进来,发展到GB18030这一代,已经把世界大多民族的文字符号都囊括在内了。虽然GB18030的字符集可谓是最全面的,但是Windows系统下中文(中国)区域对应的默认代码页还是GBK。
那么,UTF-8又是怎么回事呢?要想理解她,必须先了解Unicode是什么东东。
大一统的Unicode
Unicode是为了世界和平和统一而诞生的。高大上吧,其实原理很简单:她把世界上所有的字符都列了出来,然后给每个字符分配了一个唯一编号,不管是什么平台也不管是什么语言,这个编号都不会发生改变。这种编码方式对应的通用字符集叫做UCS(Universal Character Set,UCS)-2。USC-2后面有个数字2是因为,目前的Unicode使用16位编码空间,即每个字符对应两个字节。
说到这里,终于可以揭开UTF-8的真面目了。她的全称是:Unicode Transformation Format-8bit。简单来说,UTF-8就是Unicode的一种实现方式或者说表现形式,主要是为了降低传输成本。她是一种可变长度字符编码方式,通常一个英文字符占用一个字节,一个汉字占用三个字节。对于绝大多数汉字而言,使用GB2312/GBK/GB18030是可以用两个字节表示的,因此对于汉字来说,使用UTF-8编码的弊端是会占用更多空间,但是由于UTF-8是基于Unicode的,她的优点在于:世界通用。
关于转码
铺垫了那么多,本文的重点终于出现了,那就是不同编码方式如何转换?其实很简单,那就是通过Unicode码做桥梁,实现GB2312/GBK/GB18030和UTF-8的相互转换。
可是如果这么简单,为何中文转码总会出现各种各样的问题呢?从这里开始水就深了,非要一句话总结的话,只能说汉字博大精深啊。下面列几个数字大家感受下。
在Unicode 5.0的99089个字符中,有71226个字符与汉字有关。它们的分布如下:
Block名称 | 开始码位 | 结束码位 | 字符数 | |
CJK统一汉字 | 4E00 | 9FBB | 20924 | |
CJK统一汉字扩充A | 3400 | 4DB5 | 6582 | |
CJK统一汉字扩充B | 20000 | 2A6D6 | 42711 | |
CJK兼容汉字 | F900 | FA2D | 302 | |
CJK兼容汉字 | FA30 | FA6A | 59 | |
CJK兼容汉字 | FA70 | FAD9 | 106 | |
CJK兼容汉字补充 | 2F800 | 2FA1D | 542 |
更多相关资料请戳:
字符集和字符编码(Charset & Encoding)
Unicode、GB2312、GBK和GB18030中的汉字
GB18030编码研究以及GBK、GB18030与Unicode的映射
case分享
下面说一下我遇到的转码问题。
一句话说重点:UTF-8转GBK时,映射到Unicode码中PUA(Private Use Area,用户造字区)码位的GBK码,用php中的mb_convert_encoding函数不能正确转码。
这句话看起来比较绕,简单点说是这样子的:
UTF-8转GBK需要先将UTF-8转Unicode,这一步是没有问题的。然后从Unicode转GBK,绝大部分汉字也都是没问题的。但是,,,有那么一些汉字(查资料得知是80个),GBK的收录时间早于Unicode,所以这些汉字的GBK码对应的Unicode码最开始是用Unicode自定义区的码位来映射的。大概是因为mb_convert_encoding函数只能支持映射到Unicode非PUA码位的字符转码,所以在给这些汉字转码时出现了错误。(这是推论出来的,未经验证)
解决这个问题有两个办法:
1、升级php版本到5.4+,因为mb_convert_encoding函数从5.4起支持GB18030字符集,这80个字符中有66个在GB18030中映射到了Unicode的非PUA码位。注意,对于其余14个字符,此办法仍旧不能正确转码。
2、针对这80个特殊字符,建立一张从Unicode码到GBK码的映射表。以下为测试通过的代码:
<?php
/**
* 转码工具类
*/
class ds_TranscodingModel {
/**
* 特殊字符从unicode码到gbk码的映射
*/
private $transDict = array(
'e815' => 'fe50',
'e816' => 'fe51',
'e817' => 'fe52',
'e818' => 'fe53',
'e819' => 'fe54',
'e81a' => 'fe55',
'e81b' => 'fe56',
'e81c' => 'fe57',
'e81d' => 'fe58',
'e81e' => 'fe59',
'e81f' => 'fe5a',
'e820' => 'fe5b',
'e821' => 'fe5c',
'e822' => 'fe5d',
'e823' => 'fe5e',
'e824' => 'fe5f',
'e825' => 'fe60',
'e826' => 'fe61',
'e827' => 'fe62',
'e828' => 'fe63',
'e829' => 'fe64',
'e82a' => 'fe65',
'e82b' => 'fe66',
'e82c' => 'fe67',
'e82d' => 'fe68',
'e82e' => 'fe69',
'e82f' => 'fe6a',
'e830' => 'fe6b',
'e831' => 'fe6c',
'e832' => 'fe6d',
'e833' => 'fe6e',
'e834' => 'fe6f',
'e835' => 'fe70',
'e836' => 'fe71',
'e837' => 'fe72',
'e838' => 'fe73',
'e839' => 'fe74',
'e83a' => 'fe75',
'e83b' => 'fe76',
'e83c' => 'fe77',
'e83d' => 'fe78',
'e83e' => 'fe79',
'e83f' => 'fe7a',
'e840' => 'fe7b',
'e841' => 'fe7c',
'e842' => 'fe7d',
'e843' => 'fe7e',
'e844' => 'fe80',
'e845' => 'fe81',
'e846' => 'fe82',
'e847' => 'fe83',
'e848' => 'fe84',
'e849' => 'fe85',
'e84a' => 'fe86',
'e84b' => 'fe87',
'e84c' => 'fe88',
'e84d' => 'fe89',
'e84e' => 'fe8a',
'e84f' => 'fe8b',
'e850' => 'fe8c',
'e851' => 'fe8d',
'e852' => 'fe8e',
'e853' => 'fe8f',
'e854' => 'fe90',
'e855' => 'fe91',
'e856' => 'fe92',
'e857' => 'fe93',
'e858' => 'fe94',
'e859' => 'fe95',
'e85a' => 'fe96',
'e85b' => 'fe97',
'e85c' => 'fe98',
'e85d' => 'fe99',
'e85e' => 'fe9a',
'e85f' => 'fe9b',
'e860' => 'fe9c',
'e861' => 'fe9d',
'e862' => 'fe9e',
'e863' => 'fe9f',
'e864' => 'fea0',
);
/**
* 汉字转Unicode编码
* @param string $str 原始汉字的字符串
* @param string $encoding 原始汉字的编码
* @param boot $ishex 是否为十六进制表示(支持十六进制和十进制)
* @param string $prefix 编码后的前缀
* @param string $postfix 编码后的后缀
*/
function unicode_encode($str, $encoding = 'UTF-8', $ishex = false, $prefix = '&#', $postfix = ';') {
$str = mb_convert_encoding($str, 'UCS-2', $encoding);
$arrstr = str_split($str, 2);
$unistr = '';
for($i = 0, $len = count($arrstr); $i < $len; $i ++) {
$dec = $ishex ? bin2hex($arrstr[$i]) : hexdec(bin2hex($arrstr[$i]));
$unistr .= $prefix . $dec . $postfix;
}
return $unistr;
}
/**
* Unicode编码转汉字
* @param string $str Unicode编码的字符串
* @param string $decoding 原始汉字的编码
* @param boot $ishex 是否为十六进制表示(支持十六进制和十进制)
* @param string $prefix 编码后的前缀
* @param string $postfix 编码后的后缀
*/
function unicode_decode($unistr, $encoding = 'UTF-8', $ishex = false, $prefix = '&#', $postfix = ';') {
$arruni = explode($prefix, $unistr);
$unistr = '';
for($i = 1, $len = count($arruni); $i < $len; $i ++) {
if (strlen($postfix) > 0) {
$arruni[$i] = substr($arruni[$i], 0, strlen($arruni[$i]) - strlen($postfix));
}
$temp = $ishex ? hexdec($arruni[$i]) : intval($arruni[$i]);
$unicode = ($temp < 256) ? chr(0) . chr($temp) : chr($temp / 256) . chr($temp % 256);
$tempHex = bin2hex($unicode);
if(array_key_exists($tempHex, $this->transDict)) {
$unistr .= pack("H*",$this->transDict[$tempHex]);
} else {
$unistr .= mb_convert_encoding($unicode, $encoding, 'UCS-2');
}
}
return $unistr;
}
/**
* utf8编码转gbk编码
* @param string $str
* @return string
*/
function utf8_to_gbk($str) {
$gbkStr = mb_convert_encoding($str, 'GBK', 'UTF-8');
$utf8_str = mb_convert_encoding($gbkStr, 'UTF-8', 'GBK');
if($utf8_str == $str) {
return $gbkStr;
}
$unistr = self::unicode_encode($str, 'UTF-8');
$str = self::unicode_decode($unistr, 'GBK');
return $str;
}
?>