mb_detect_encoding() 在實務中常因檢測順序、置信度閾值導致誤判。本文提供一組「暴力驗證法」函數:以 mb_convert_encoding($str, $to, $from) 雙向轉碼後比對 MD5 雜湊,若雜湊不變則判定為該編碼。原理簡單、無依賴、準確率高,附帶自動讀取轉碼一體函數。
若字串 $str 真實編碼為 GBK,則:
mb_convert_encoding($str, 'GBK', 'GBK') === $str // true,MD5 相同
mb_convert_encoding($str, 'UTF-8', 'GBK') != $str // 內容改變,MD5 不同
遍歷候選編碼列表,首個通過「雙向轉碼 MD5 不變」測試者即為真實編碼。
<?php
/**
* 偵測檔案/字串編碼
* @param string $input 檔案路徑或字串內容
* @param bool $isFile true=檔案路徑, false=字串
* @return string|null 編碼名稱 (GBK, UTF-8, UTF-16LE, UTF-16BE, ISO-8859-1) 或 null
*/
function detectEncoding($input, bool $isFile = true): ?string
{
$str = $isFile ? file_get_contents($input) : $input;
if ($str === false || $str === '') return null;
$candidates = ['GBK', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'ISO-8859-1'];
$originalMd5 = md5($str);
foreach ($candidates as $enc) {
// 雙向轉碼:目標編碼 -> 原編碼
$converted = mb_convert_encoding($str, $enc, $enc);
if (md5($converted) === $originalMd5) {
return $enc;
}
}
return null;
}
/**
* 自動偵測編碼並轉為指定編碼讀取
* @param string $file 檔案路徑
* @param string $targetCharset 目標輸出編碼 (預設 UTF-8)
* @return string 轉碼後內容,失敗回傳空字串
*/
function autoReadFile(string $file, string $targetCharset = 'UTF-8'): string
{
$str = file_get_contents($file);
if ($str === false) return '';
$candidates = ['GBK', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'ISO-8859-1'];
$originalMd5 = md5($str);
foreach ($candidates as $enc) {
$converted = mb_convert_encoding($str, $enc, $enc);
if (md5($converted) === $originalMd5) {
// 偵測成功,轉為目標編碼輸出
return mb_convert_encoding($str, $targetCharset, $enc);
}
}
// 所有候選都不符,嘗試強制按 UTF-8 讀取並忽略錯誤字節
return mb_convert_encoding($str, $targetCharset, 'UTF-8');
}
/**
* 批次轉碼目錄下所有檔案
*/
function batchConvertDirectory(string $dir, string $targetCharset = 'UTF-8', array $exts = ['php', 'html', 'css', 'js', 'txt']): array
{
$results = ['success' => [], 'failed' => []];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
if (!$file->isFile()) continue;
if (!in_array(strtolower($file->getExtension()), $exts)) continue;
$content = autoReadFile($file->getPathname(), $targetCharset);
if ($content !== '' && file_put_contents($file->getPathname(), $content) !== false) {
$results['success'][] = $file->getPathname();
} else {
$results['failed'][] = $file->getPathname();
}
}
return $results;
}
<?php
require_once 'encoding_helper.php';
// 1. 偵測單一檔案
$file = './data/legacy_gbk.csv';
$encoding = detectEncoding($file);
echo "偵測編碼: " . ($encoding ?? '未知') . "\n";
// 2. 自動讀取並轉為 UTF-8
$content = autoReadFile($file, 'UTF-8');
echo "前 200 字元:\n" . mb_substr($content, 0, 200) . "\n";
// 3. 批次轉碼專案目錄
$report = batchConvertDirectory(__DIR__ . '/src', 'UTF-8');
echo "成功: " . count($report['success']) . " 失敗: " . count($report['failed']) . "\n";
| 優點 | 缺點/限制 |
|---|---|
| 無第三方依賴,純 PHP 核心函數 | 需讀取完整檔案進記憶體,超大檔 (>100MB) 可能 OOM |
| 邏輯單一,無誤判參數調校困擾 | 純 ASCII 內容 (純英文/數字) 會被誤判為 ISO-8859-1 (列表首位) |
| 支援 UTF-16 LE/BE 等雙位元組編碼 | 不支援 Big5、Shift_JIS 等未列入候選表的編碼 |
| 可擴展候選列表適應專案需求 | MD5 碰撞理論風險極低但存在 (可升級為 SHA256) |
EF BB BF (UTF-8 BOM)、FF FE (UTF-16LE)、FE FF (UTF-16BE),有 BOM 直接確定編碼,跳過 MD5 驗證。finfo 擴展:finfo_buffer(finfo_open(FILEINFO_MIME_ENCODING), $str) 作為輔助參考,雙重驗證提高置信度。這組函數庫已在多個遷移專案 (GBK→UTF-8、Big5→UTF-8) 中穩定運行,適合 CI/CD 流程中自動化編碼修正與品質把關。