iconv、mb_convert_encoding 都要求已知源編碼。但在爬蟲、郵件解析、舊系統對接等場景中,源編碼往往未知。本文提供一個基於正則的 UTF-8 檢測函數,自動識別並轉換至目標編碼。
/**
* 自動檢測編碼並轉換
* @param string $string 待轉換字符串
* @param string $target 目標編碼(默認 gbk)
* @return string
*/
function detect_encoding($string, $target = 'gbk') {
// RFC 3629 兼容的 UTF-8 正則(修正版)
$utf8_regex = '%^(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # 2 字節
| \xE0[\xA0-\xBF][\x80-\xBF] # 3 字節首字節 E0
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # 3 字節常規
| \xED[\x80-\x9F][\x80-\xBF] # 3 字節代理區
| \xF0[\x90-\xBF][\x80-\xBF]{2} # 4 字節首字節 F0
| [\xF1-\xF3][\x80-\xBF]{3} # 4 字節常規
| \xF4[\x80-\x8F][\x80-\xBF]{2} # 4 字節尾部
)*$%xs';
$is_utf8 = (bool)preg_match($utf8_regex, $string);
if ($is_utf8) {
return ($target === 'utf8' || $target === 'utf-8')
? $string
: mb_convert_encoding($string, $target, 'UTF-8');
} else {
// 非 UTF-8 按中文常見編碼嘗試轉換
return mb_convert_encoding($string, $target, 'GBK,GB2312,BIG5');
}
}
%...%xs:x 忽略空白註釋,s 讓 . 匹配換行(此處未用)preg_match('//u', $str),誤判率極低$tests = [
'你好世界', // UTF-8
iconv('UTF-8', 'GBK', '你好'), // GBK
'测试', // 可能是 GBK/Big5
];
foreach ($tests as $t) {
echo "原始: " . bin2hex($t) . "\n";
echo "→ UTF-8: " . detect_encoding($t, 'utf-8') . "\n";
echo "→ GBK: " . detect_encoding($t, 'gbk') . "\n";
echo "---\n";
}
| 場景 | 問題 | 建議 |
|---|---|---|
| 極短字符串(<5字節) | 誤判率高 | 結合 mb_detect_encoding 多重驗證 |
| 混合編碼文本 | 無法逐字符識別 | 按行/塊分割檢測 |
| 非中文語言(日文 Shift_JIS、韓文 EUC-KR) | 識別為非 UTF-8 後按中文編碼轉換會亂碼 | 擴展檢測列表或引入 uchardet 庫 |
| 性能敏感場景 | 正則匹配較慢 | 僅在編碼未知時調用,已知編碼直接 mb_convert_encoding |
function smart_convert($str, $to = 'UTF-8') {
// 1. 已知編碼優先(如 HTTP Content-Type、Meta Charset)
// 2. 嘗試 mb_detect_encoding(需啟用 mbstring.encoding_translation)
$detected = mb_detect_encoding($str, 'UTF-8, GBK, BIG5, ASCII', true);
if ($detected) return mb_convert_encoding($str, $to, $detected);
// 3. 兜底:正則檢測 UTF-8
return detect_encoding($str, $to);
}