判斷字串是否為合法 UTF-8 是資料清洗、匯入匯出、安全過濾的基礎需求。PHP 沒有單一完美函數,本文對比 4 種主流做法:mb_detect_encoding、PCRE 正則、手寫位元檢查、圖騰式檢測,並給出選型建議。
<?php
function is_utf8_mb(string $str): bool {
return mb_detect_encoding($str, 'UTF-8', true) === 'UTF-8';
}
// 第三個參數 strict=true 關鍵,否則會誤判 ISO-8859-1 為 UTF-8
?>
mbstring 擴充套件、極短字串(<3 bytes)誤判率較高<?php
function is_utf8_pcre(string $str): bool {
// W3C 發布的 UTF-8 驗證正則(修正版)
$pattern = '/^(
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # 2 bytes
| \xE0[\xA0-\xBF][\x80-\xBF] # 3 bytes (排除 overlong)
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # 3 bytes
| \xED[\x80-\x9F][\x80-\xBF] # 3 bytes (排除 surrogate)
| \xF0[\x90-\xBF][\x80-\xBF]{2} # 4 bytes (planes 1-3)
| [\xF1-\xF3][\x80-\xBF]{3} # 4 bytes (planes 4-15)
| \xF4[\x80-\x8F][\x80-\xBF]{2} # 4 bytes (plane 16)
)*$/xs';
return (bool)preg_match($pattern, $str);
}
?>
<?php
function is_utf8_manual(string $str): bool {
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
$c = ord($str[$i]);
if ($c <= 0x7F) continue; // ASCII
if ($c >= 0xC2 && $c <= 0xDF) $bytes = 2;
elseif ($c >= 0xE0 && $c <= 0xEF) $bytes = 3;
elseif ($c >= 0xF0 && $c <= 0xF4) $bytes = 4;
else return false; // 非法開頭位元組
if ($i + $bytes > $len) return false; // 截斷
for ($j = 1; $j < $bytes; $j++) {
$b = ord($str[$i + $j]);
if ($b < 0x80 || $b > 0xBF) return false; // 非法延續位元組
}
$i += $bytes - 1;
}
return true;
}
?>
<?php
function is_utf8_chinese(string $str): bool {
// 僅檢測常見中文 UTF-8 範圍:E4-B8-80 到 E9-BE-A0
return (bool)preg_match('/^[\xE4-\xE9][\x80-\xBF]{2}+$/', $str);
}
// ⚠️ 僅適用「純中文」字串,英文、標點、Emoji 會誤判
?>
| 方法 | 耗時(ms) | 準確度 | 依賴 |
|---|---|---|---|
| mb_detect_encoding (strict) | ~45 | ★★★★☆ | mbstring |
| PCRE 正則 (W3C) | ~120 | ★★★★★ | 無 |
| 手寫位元掃描 | ~210 | ★★★★☆ | 無 |
| 圖騰式 (中文) | ~30 | ★★☆☆☆ | 無 |
mb_detect_encoding($str, 'UTF-8', true) — 平衡準確與效能。utf8_valid() (PECL) 或 iconv('UTF-8', 'UTF-8', $str) === $str 技巧。<?php
function is_utf8_iconv(string $str): bool {
// 失敗回傳 false,成功回傳轉換後字串
$converted = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
return $converted !== false && $converted === $str;
}
// //IGNORE 會移除非法位元組,若結果不同則原字串非純 UTF-8
?>