PHP中编码检测

背景:

  • 编码问题在Python同学眼中应该是老生常谈的,本文谈下PHP中常见的编码相关检测方法及局限
  • 数据在写入时决定了编码形式,而由于历史变更可能存在历史数据中写入编码不同
  • 编码检测的目的:检测数据的编码形式,正确解码及界面展示

PHP中不同编码检测方法

mb_detect_encoding检测

mb_detect_encoding 函数中 $encoding_list 参数中编码顺序不同,会影响最终检测的结果。

// 用法:
$coding = mb_detect_encoding($unameGbk, array('UTF-8', 'GBK'), true);

$coding = mb_detect_encoding($unameGbk, array('GBK', 'UTF-8'), true);

// 实例
var_dump(mb_detect_encoding('hello', ['utf8', 'gbk'])); // utf8
var_dump(mb_detect_encoding('hello', ['gbk', 'utf8'])); // CP936 == gbk

自定义实现的is_utf8()和is_gbk()方法

这个来源是流传较广的一种写法

public static function is_utf8($str)
{
    return 1 == preg_match('%^(?:[\x09\x0A\x0D\x20-\x7E]' .    // ASCII
            '| [\xC2-\xDF][\x80-\xBF]' .                //non-overlong 2-byte
            '| \xE0[\xA0-\xBF][\x80-\xBF]' .            //excluding overlongs
            '| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}' .    //straight 3-byte
            '| \xED[\x80-\x9F][\x80-\xBF]' .            //excluding surrogates
            '| \xF0[\x90-\xBF][\x80-\xBF]{2}' .        //planes 1-3
            '| [\xF1-\xF3][\x80-\xBF]{3}' .            //planes 4-15
            '| \xF4[\x80-\x8F][\x80-\xBF]{2}' .        //plane 16
            ')*$%xs', $str);
}

public static function is_gbk($str)
{
    //return preg_match('%^(?:[\x81-\xFE]([\x40-\x7E]|[\x80-\xFE]))*$%xs', $str);
    return 1 == preg_match('%^(?:[\x81-\xFE][\x40-\x7E]' .
            '| [\x81-\xFE][\x80-\xFE]' .
            '| [\x00-\x80])*$%xs', $str);
}

iconv同编码转换后判等方式

  • 通过 iconv 判断转换前后的内容是否一致,作为编码检测的依据
  • 优劣
    • 必须指定检测范围
    • 在明确范围的情况下,检测准确性最佳
function detectEncoding ($str) {
    foreach (array('GBK', 'UTF-8') as $v) {
        if ($str === @iconv($v, $v . '//IGNORE', $str)) {
            return $v;
        }
    }
}

实例验证

先看个正常的例子:

  • 小知识:GBK 和 CP936可以认为是同一种编码的不同叫法,CP936是GBK的实时实现。
$str = '天下相思';
$strNew = mb_convert_encoding($str, 'gbk', 'utf8'); // 从utf8转码至gbk,讲道理strNew只能是gbk吧


$coding = mb_detect_encoding($strNew, array('UTF-8', 'GBK'), true); // CP936
$coding = mb_detect_encoding($strNew, array('GBK', 'UTF-8'), true); // CP936
// 无论哪种方式检测,结果都是CP936,这就比较统一
  • 再一个极端的例子,不论哪种方式检测,都无法做到100%正确?
  • 此时严重依赖detectList中指定的待检测编码的顺序,也就是说可能误判为utf8,而此时如果输出则会乱码(实际并非utf8编码)
$str = '鸶姬'; // 本地utf8编码
$strNew = mb_convert_encoding($str, 'gbk', 'utf8'); // 从utf8转码至gbk,讲道理strNew只能是gbk吧

// 请问此时$strNew的编码是什么?编码检测工具检测出的结果如何?

$coding = mb_detect_encoding($strNew, array('UTF-8', 'GBK'), true); // UTF-8
$coding = mb_detect_encoding($strNew, array('GBK', 'UTF-8'), true); // CP936

var_dump(is_utf8($strNew)); // true
var_dump(is_gbk($strNew)); // true

结论&注意事项

detectList
// 注意,这里仅支持gbk/utf8的编码检测,且数据源多数为gbk编码
// 函数副作用:可能存在字符串检测结果既是gbk又是utf8,此方法会优先返回gbk
// 根据有限的几条测试情况,这种情况下,gbk->utf8的转码并不会改变字节码,结果一致
// 所以还是有局限性的,请明确使用范围和影响
function detectEncoding ($str) {
    foreach (array('GBK', 'UTF-8') as $v) {
        if ($str === @iconv($v, $v . '//IGNORE', $str)) {
            return $v;
        }
    }
}

// 输入字符串,编码待确定,下面统一转换为utf8编码
$str = 'xxxx';
if(detectEncoding($str) == 'GBK') { //  源字符串为gbk编码
    $strUtf8 = mb_convert_encoding($str, 'utf8', 'gbk'); // 从gbk转码为utf8
} else { // 源字符串为utf8编码
    $strUtf8 = $str;
}
// 最终utf8编码的字符串保存在变量$strUtf8中
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章