前面的 PHPExcel 读取 Excel 数据并导入数据库,在组合脚本的过程中,数据库插入数据后发生了中文乱码现象。

经过一番波折 ,虽然最终确定了造成插入数据中文乱码原因,但对 PHP 字符编码转换函数的测试也有一些小发现。

为什么数据库插入数据会有中文乱码

数据库数据出现中文乱码可以从三个方面考虑:

  • 数据库存储字符集与查看环境字符集不同
  • 插入数据字符编码与数据库存储字符集不同
  • 客户端和服务器之间传递字符的编码规则不同

这三个方面主要涉及的点也是三个,数据库、MySQL 服务器和数据。只要统一这三者的字符集,就能确保不会出现乱码。检查了一下 ,数据库字符集为 utf8;数据是从 Excel 中读取的 ,不确定;MySQL 服务器也不确定,但可以用指令查看。

MySQL 服务器编码规则查看

# 进入 MySQL 命令行
mysql> show variables like 'character%';

MySQL 字符编码

发现好多项都不是 utf8,最终结果是,执行 set names utf8 之后,再插入数据显示正常。

那么中间那些变量值改变了呢?

执行后的 MYSQL 字符编码

所以 set names utf8 实际修改了 character_set_client、character_set_connection、character_set_results 这三项的值。这三项定义了客户端、连接和返回结果的字符集。

如何确定读取数据的字符编码

PHP 主要有两个修改字符串编码的函数 iconv 和 mb_convert_encoding,一个判断字符串编码的函数 mb_detect_encoding。

iconv($encoding, "UTF-8//IGNORE", $str)
mb_convert_encoding($text, 'UTF-8', $encoding);

两者区别:

一般情况下用 iconv,只有当遇到无法确定原编码是何种编码,或者iconv转化后无法正常显示时才用mb_convert_encoding 函数。

注意:要安装相应的 mbstring 扩展,并在配置文件中启动之后,mb_* 方法才能生效。

所以在不确定字符编码的情况下,必须有 mb_detect_encoding 协助才行。

mb_detect_encoding($str ,array('ASCII','GB2312','GBK','UTF-8'), true);
mb_detect_encoding($str, mb_detect_order(), false)

第二个参数给出优先判断的字符编码列表,因为字符编码太多,这样做可以一定程度的提高函数运行效率,默认为mb_detect_order()。测试了一下,mb_detect_order() 输出一数组,包含 ‘ASCII’ 和 ‘UTF-8’ 两个元素。最后一个参数表示是否使用严格的编码检测,默认为 false。

在网上找到一个万金油版的组合使用方法:

    function ConvertToUTF8($text){

        $encoding = mb_detect_encoding($text, mb_detect_order(), false);

        if($encoding == "UTF-8")
        {
            $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
        }


        $out = iconv(mb_detect_encoding($text, mb_detect_order(), false), "UTF-8//IGNORE", $text);


        return $out;
    }

如果执行 iconv 报错 Detected an incomplete multibyte character,可以尝试使用这个方法来转换。

我之前适配过的一个处理抓取 html 文档的标题的写法:

    $encode = mb_detect_encoding($title, array("ASCII","GB2312","GBK","UTF-8"));

    if ($encode == "EUC-CN") {
        //    $title = mb_convert_encoding($title, "GBK", $encode);
        $title = iconv($encode, "UTF-8", $title);
    } elseif (!in_array($encode, array("ASCII", "CP936", "GB2312", "UTF-8", "GBK"))) {
        $title = iconv($encode, "GBK", $title);
    }

诡异的 CP936 编码无法转换成 UTF-8

CP936 转化为 UTF-8 失败?这是一个错误的命题,因为 mb_detect_encoding 并不是完全准确的判断字符编码,而是根据第二个参数,按照顺序去匹配字符编码。如果满足了某个字符编码,则会立即返回该编码。

$ php -r 'echo mb_detect_encoding("ABC", mb_detect_order(), true);'
ASCII
$ php -r 'echo mb_detect_encoding("ABC", mb_detect_order(), false);'
ASCII
$ php -r 'echo mb_detect_encoding("ABC-DEF", mb_detect_order(), false);'
ASCII
$ php -r 'echo mb_detect_encoding("ABC-DEF?", mb_detect_order(), true);'
UTF-8
$ php -r 'echo mb_detect_encoding("ABC-DEF?", mb_detect_order(), false);'
UTF-8
$ php -r "echo mb_detect_encoding('我是白羊座', mb_detect_order(), false);"       
UTF-8

...

$ php -r "echo mb_detect_encoding('我是金牛座', array('ASCII','GB2312','GBK','UTF-8'), false);"
CP936

得到的结果很尴尬,为了执行效率而特意适配使用的 array('ASCII','GB2312','GBK','UTF-8') 竟然成了导致判断错误的罪魁祸首。也就是说,字符串本来就是‘UTF-8’,转化过程中字符编码没有有任何变化,转化后又通过这个 mb_detect_encoding('我是金牛座', array('ASCII','GB2312','GBK','UTF-8'), false) 方法,判断的结果当然还是错误的结果。

并不是 CP936 编码无法转换成 UTF-8,而是判断出的编码有误。

所以在是用 mb_detect_encoding 方法时,不加任何多余参数判断是最准确的:

mb_detect_encoding('我是白羊座')