處理外部資料(CSV 匯入、API 串接、舊系統遷移)時,常面臨編碼未知、混雜編碼問題。本文提供一個健壯的 array_iconv() 函數,支援字串/陣列遞迴、多編碼自動偵測、錯誤容忍,並分析 mb_detect_encoding 的局限與優化策略。
/**
* 自動偵測編碼並轉換為目標編碼(預設 UTF-8)
* @param string|array $data 輸入資料
* @param string $output 目標編碼
* @return string|array 轉換後資料
*/
function array_iconv($data, $output = 'UTF-8') {
// 常見編碼偵測順序(越前面優先級越高)
$encodeList = array(
'UTF-8', 'ASCII', 'GBK', 'GB2312', 'BIG5',
'JIS', 'EUC-JP', 'SJIS', 'EUC-JP-WIN', 'SJIS-WIN'
);
// 單一字串處理
if (!is_array($data)) {
$detected = mb_detect_encoding($data, $encodeList, true); // strict 模式
if ($detected === false) {
// 無法偵測時嘗試假設為 UTF-8,失敗則原樣返回
if (!mb_check_encoding($data, 'UTF-8')) {
// 最後手段:強制轉換並忽略錯誤字元
return @iconv('GBK', $output . '//IGNORE', $data);
}
return $data; // 已是 UTF-8
}
return mb_convert_encoding($data, $output, $detected);
}
// 陣列遞迴處理
$result = array();
foreach ($data as $key => $val) {
$newKey = array_iconv($key, $output); // 鍵值也需轉換
if (is_array($val)) {
$result[$newKey] = array_iconv($val, $output);
} else {
$result[$newKey] = array_iconv($val, $output);
}
}
return $result;
}
class EncodingConverter {
private static $detectOrder = array('UTF-8', 'GBK', 'BIG5', 'GB2312');
private static $cache = array(); // 簡單快取:字串 hash => 偵測編碼
public static function convert($data, $to = 'UTF-8', $detectOrder = null) {
if ($detectOrder) self::$detectOrder = $detectOrder;
return self::doConvert($data, $to);
}
private static function doConvert($data, $to) {
if (!is_array($data)) {
$hash = md5($data);
if (isset(self::$cache[$hash])) {
$from = self::$cache[$hash];
} else {
$from = mb_detect_encoding($data, self::$detectOrder, true);
if ($from === false) {
$from = mb_check_encoding($data, 'UTF-8') ? 'UTF-8' : 'GBK'; // 預設回退
}
self::$cache[$hash] = $from;
}
return mb_convert_encoding($data, $to, $from);
}
$result = array();
foreach ($data as $k => $v) {
$result[self::doConvert($k, $to)] = self::doConvert($v, $to);
}
return $result;
}
}
| 陷阱 | 現象 | 對策 |
|---|---|---|
| 偵測順序影響結果 | GBK/UTF-8 共用字元範圍,順序不同結果不同 | 明確指定 $encodeList,將 UTF-8 放第一優先 |
| 短字串誤判率高 | 「測試」兩字在 GBK/UTF-8 皆合法 | 結合 mb_check_encoding($str, 'UTF-8') 二次驗證 |
| 嚴格模式回傳 false | 含非法位元組序列時 | 使用 //IGNORE 或 //TRANSLIT 參數 |
| 效能:重複偵測同字串 | 大量資料處理緩慢 | 快取偵測結果(如進階版) |
// 1. CSV 匯入(常為 Big5/GBK)
$csvData = file_get_contents('data.csv');
$rows = str_getcsv($csvData, "\n");
foreach ($rows as $row) {
$fields = str_getcsv($row);
$fields = array_iconv($fields); // 整行轉 UTF-8
Model::importRow($fields);
}
// 2. API 回應編碼不一
$response = file_get_contents('https://legacy-api/data');
$data = json_decode(array_iconv($response), true);
// 3. 表單提交編碼修正(舊瀏覽器可能送 Big5)
$_POST = array_iconv($_POST);
$_GET = array_iconv($_GET);
iconv('GBK', 'UTF-8//IGNORE', $data) 比 mb_convert_encoding 快 2-3 倍。mb_convert_encoding($data, 'UTF-8', 'GBK') 跳過偵測。array_chunk($data, 1000) 避免記憶體峰值。編碼轉換的核心原則:明確優於推測、快取優於重複計算、容錯優於拋出異常。本文提供的 array_iconv() 與 EncodingConverter 類別,已在多個遷移專案驗證穩定。建議根據實際來源編碼分布,調整 $encodeList 順序與快取策略。