最近在搞一个国际化的项目,需要处理各种语言的字符转换。这不,又遇到了Unicode转中文的老问题。说实话,这东西说简单也简单,说坑也多。今天就来聊聊这个,顺便分享几个我踩过的坑。
先来个最简单的例子,假设你有个Unicode字符串:"\u4f60\u597d",想把它转成中文"你好"。用PHP简直不要太简单:
$unicodeStr = "\u4f60\u597d";
echo $unicodeStr; // 直接输出就是"你好"
看,PHP就是这么贴心,自动帮你转好了。但是!注意这个但是,事情往往没这么简单。
现实中的坑往往出现在JSON数据里。比如从API拿到这样一个JSON:
{"text":"\u4f60\u597d"}
你用json_decode解析后:
$data = json_decode('{"text":"\u4f60\u597d"}', true);
echo $data['text']; // 还是输出"你好"
看起来没问题?别急,坑来了。如果这个JSON里的Unicode是带斜杠的,比如:
{"text":"\\u4f60\\u597d"}
这时候解析出来就是字符串"\u4f60\u597d",而不是转换后的中文。这时候就需要手动处理了:
function unicodeToChinese($str) {
return preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($match) {
return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
}, $str);
}
$str = "\\u4f60\\u597d";
echo unicodeToChinese($str); // 输出"你好"
这个函数我用了好多年,稳如老狗。原理就是把\u后面的4位十六进制数转成UCS-2编码,再转成UTF-8。
说到编码转换,不得不提mb_convert_encoding这个函数。有一次我遇到个奇葩问题,从数据库读出来的Unicode字符串死活转不成中文。调试了半天才发现,数据库里的字符串居然是"U+4F60U+597D"这种格式。当时我的表情大概是这样的:(╯°□°)╯︵ ┻━┻
最后解决办法是这样的:
function weirdUnicodeToChinese($str) {
return preg_replace_callback('/U\+([0-9a-fA-F]{4})/', function ($match) {
}
$str = "U+4F60U+597D";
echo weirdUnicodeToChinese($str); // 还是输出"你好"
还有一个常见场景是从HTML实体转中文。比如"你好"对应的HTML实体是"你好"。处理这个可以用html_entity_decode:
$htmlEntity = "你好";
echo html_entity_decode($htmlEntity, ENT_QUOTES, 'UTF-8'); // 输出"你好"
注意这里的ENT_QUOTES参数,它会转换双引号和单引号。如果你不确定编码,最好明确指定UTF-8,不然可能会得到一堆乱码。
说到乱码,我不得不提Windows命令行这个坑货。有次我写了个脚本处理Unicode,在IDE里运行好好的,一到命令行就乱码。后来发现是命令行默认编码问题,解决方案是在脚本开头加上:
if (PHP_OS == 'WINNT') {
exec('chcp 65001');
}
这个命令把Windows命令行切换到UTF-8编码。不过要注意,改了编码后,某些老旧程序可能会出问题。
再来说说性能问题。如果你要处理大量Unicode字符串,直接用正则替换可能会比较慢。这时候可以考虑先json_encode再json_decode:
$temp = json_decode('"' . str_replace('"', '\"', $str) . '"');
echo $temp; // 输出"你好"
这个方法利用了JSON解析器的Unicode转换功能,速度比正则替换快不少。不过要注意处理字符串中的引号,不然会报错。
最后分享一个真实案例。有次对接第三方API,返回的JSON里混合了Unicode和HTML实体,大概是这样的:
{"message":"Hello \\u4f60\\u597d, welcome to 你好 world"}
处理这种混合字符串,我写了这样的函数:
function mixedDecode($str) {
// 先处理Unicode
$str = preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($match) {
// 再处理HTML实体
return html_entity_decode($str, ENT_QUOTES, 'UTF-8');
}
$str = "Hello \\u4f60\\u597d, welcome to 你好 world";
echo mixedDecode($str); // 输出"Hello 你好, welcome to 你好 world"
这个函数先处理Unicode,再处理HTML实体,顺序不能反,不然可能会出问题。
总结一下,Unicode转中文看似简单,实际应用中会遇到各种奇葩情况。关键是要理解背后的编码原理,遇到问题时知道该往哪个方向排查。记住,大多数编码问题都是因为没搞清楚输入到底是什么格式,以及输出需要什么格式。
最后的最后,如果你还在用PHP5...算了不说了,升级到PHP8,处理Unicode的性能和稳定性都好很多。毕竟现在都2023年了,别再用那些老古董了。