偵測原理
- ASCII 字元 (0-127) 在所有編碼中相同,單位元組。
- UTF-8 中文字元:3 位元組,首位元組
1110xxxx (0xE0-0xEF),後續位元組 10xxxxxx (0x80-0xBF)。
- GB2312/GBK 中文字元:2 位元組,首位元組
1xxxxxxx (0x81-0xFE),次位元組 1xxxxxxx (0x40-0xFE, 非 0x7F)。
自動偵測並轉碼函數
<?php
/**
* 自動偵測字串編碼 (UTF-8/GB2312) 並轉換至目標編碼
*
* @param string $str 輸入字串
* @param string $targetEnc 目標編碼 (預設 UTF-8)
* @return string
*/
function safeEncoding(string $str, string $targetEnc = 'UTF-8'): string {
$detected = 'UTF-8';
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
$c = ord($str[$i]);
if ($c < 128) continue; // ASCII
// 嘗試 UTF-8 三位元組模式
if (($c & 0xE0) === 0xE0) { // 1110xxxx
if ($i + 2 < $len) {
$c1 = ord($str[$i+1]);
$c2 = ord($str[$i+2]);
if (($c1 & 0xC0) === 0x80 && ($c2 & 0xC0) === 0x80) {
$detected = 'UTF-8';
break;
}
}
}
// 嘗試 GBK 兩位元組模式
if (($c & 0x80) === 0x80) { // 1xxxxxxx
if ($i + 1 < $len) {
$c1 = ord($str[$i+1]);
if (($c1 & 0x80) === 0x80) { // 簡易判斷
$detected = 'GB2312';
break;
}
}
}
}
if (strtoupper($detected) === strtoupper($targetEnc)) {
return $str;
}
return iconv($detected, $targetEnc . '//IGNORE', $str);
}
// 測試
$utf8 = "測試字串";
$gbk = iconv('UTF-8', 'GBK', $utf8);
echo safeEncoding($utf8) . "\n"; // 正常
echo safeEncoding($gbk) . "\n"; // 自動偵測為 GB2312 並轉回 UTF-8
?>
更健壯的替代方案
- mb_detect_encoding():內建多編碼偵測,支援編碼列表優先序。
<?php
$enc = mb_detect_encoding($str, ['UTF-8', 'GBK', 'GB2312', 'BIG5', 'ASCII'], true);
if ($enc) $str = iconv($enc, 'UTF-8//IGNORE', $str);
?>
- composer require patchwork/utf8:純 PHP 實作的 UTF-8 正規化、清理、偵測庫,無需
mbstring / iconv 擴展。
<?php
use Patchwork\Utf8;
$clean = Utf8::toUtf8($mixedEncodingStr); // 自動偵測並轉 UTF-8
?>
檔案層面編碼檢查與轉換
- 檢查 BOM:
substr($content, 0, 3) === "\xEF\xBB\xBF"
- 批次轉碼工具:
iconv -f GBK -t UTF-8 input.txt > output.txt 或 PHP 腳本遞迴處理目錄。