字串反轉在單位元組編碼 (ASCII/ISO-8859) 中簡單易行,但 UTF-8 多位元組字元、Emoji (ZWJ 序列)、組合字元若按位元組或 Code Point 反轉會導致亂碼或語義破壞。本文提供三層級解決方案:mbstring Code Point 級、intl Grapheme Cluster 級、純 PHP 降級實現。
$str = 'Hello世界👨👩👧👦'; // ASCII + 中文 + 家庭 Emoji (ZWJ 序列) echo "原始: $str\n"; echo "strlen: " . strlen($str) . "\n"; // 位元組數 echo "mb_strlen: " . mb_strlen($str) . "\n"; // Code Point 數 echo "grapheme_strlen: " . grapheme_strlen($str) . "\n"; // 用戶感知字元數 // ❌ 位元組級反轉 (strrev) -> 亂碼 echo "strrev: " . strrev($str) . "\n"; // ❌ Code Point 級反轉 (mbstring) -> Emoji 內部順序錯亂 $arr = []; for ($i = 0; $i < mb_strlen($str); $i++) $arr[] = mb_substr($str, $i, 1); echo "mb_rev: " . implode(array_reverse($arr)) . "\n"; // ✅ Grapheme Cluster 級反轉 (intl) -> 正確 $clusters = grapheme_str_split($str); // PHP 8.0+ echo "grapheme_rev: " . implode(array_reverse($clusters)) . "\n";
/**
* UTF-8 字串反轉 (Code Point 級)
* 適用:一般多語言文字,不含複雜 Emoji/組合字元
* 需求:mbstring 擴展 (PHP 核心內建)
*/
function mbStrRev(string $str, string $encoding = 'UTF-8'): string {
if (!mb_check_encoding($str, $encoding)) {
throw new InvalidArgumentException("Invalid $encoding string");
}
$len = mb_strlen($str, $encoding);
$rev = '';
for ($i = $len - 1; $i >= 0; $i--) {
$rev .= mb_substr($str, $i, 1, $encoding);
}
return $rev;
}
// 測試
echo mbStrRev('Hello世界') . "\n"; // 界世olleH
echo mbStrRev('中文測試') . "\n"; // 試測文中
/**
* UTF-8 字串反轉 (Grapheme Cluster 級)
* 適用:所有 Unicode 文字,含 Emoji ZWJ 序列、組合字元、旗幟 Emoji
* 需求:intl 擴展 (pecl install intl / apt install php-intl)
* PHP 8.0+ 可用 grapheme_str_split()
*/
function graphemeStrRev(string $str): string {
if (!extension_loaded('intl')) {
throw new RuntimeException('intl extension required for grapheme operations');
}
// PHP 8.0+
if (function_exists('grapheme_str_split')) {
return implode(array_reverse(grapheme_str_split($str)));
}
// PHP 7.x 降級實現
$clusters = [];
$len = grapheme_strlen($str);
for ($i = 0; $i < $len; $i++) {
$clusters[] = grapheme_substr($str, $i, 1);
}
return implode(array_reverse($clusters));
}
// 測試
$emoji = '👨👩👧👦'; // U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466
echo graphemeStrRev("AB{$emoji}CD") . "\n"; // DC👨👩👧👦BA (Emoji 完整保持)
echo graphemeStrRev('e\u0301f') . "\n"; // fé (é = e + ́ 組合字元正確反轉)
/**
* UTF-8 字串反轉 (純 PHP,按 Code Point)
* 原理:解析 UTF-8 位元組序列判斷字元邊界
* 注意:不處理組合字元/ZWJ,僅保證不產生斷碼
*/
function utf8StrRev(string $str): string {
$bytes = strlen($str);
$rev = '';
for ($i = $bytes - 1; $i >= 0; ) {
$byte = ord($str[$i]);
// 判斷 UTF-8 字元首位元組模式
if (($byte & 0x80) === 0x00) { // 0xxxxxxx (ASCII)
$charLen = 1;
} elseif (($byte & 0xE0) === 0xC0) { // 110xxxxx (2 bytes)
$charLen = 2;
} elseif (($byte & 0xF0) === 0xE0) { // 1110xxxx (3 bytes)
$charLen = 3;
} elseif (($byte & 0xF8) === 0xF0) { // 11110xxx (4 bytes)
$charLen = 4;
} else {
// 非法位元組,視為單字節
$charLen = 1;
}
$start = $i - $charLen + 1;
$rev .= substr($str, $start, $charLen);
$i = $start - 1;
}
return $rev;
}
// 測試
echo utf8StrRev('Hello世界') . "\n"; // 界世olleH
| 方案 | 擴展依賴 | 正確性 | 效能 (相對) | 適用場景 |
|---|---|---|---|---|
| strrev | 無 | ❌ 破壞多位元組 | 1x (最快) | 純 ASCII / 二進制資料 |
| utf8StrRev (純 PHP) | 無 | ✅ 無斷碼 / ❌ 組合字元錯誤 | ~5x | 無擴展環境、簡單多語言 |
| mbStrRev (mbstring) | mbstring | ✅ 無斷碼 / ❌ ZWJ/組合字元錯誤 | ~3x | 一般業務、CJK 文字、簡單 Emoji |
| graphemeStrRev (intl) | intl | ✅ 完全正確 (Unicode 標準) | ~4x | 國際化產品、Emoji 密集、無障礙要求 |
RUN docker-php-ext-install intl,佔用極小,功能極大 (日期、數字、貨幣、字串、IDN 等)。class UnicodeString {
public static function reverse(string $str): string {
if (extension_loaded('intl')) return graphemeStrRev($str);
if (extension_loaded('mbstring')) return mbStrRev($str);
return utf8StrRev($str);
}
public static function length(string $str): int {
if (extension_loaded('intl')) return grapheme_strlen($str);
if (extension_loaded('mbstring')) return mb_strlen($str);
return strlen($str); // fallback
}
public static function substr(string $str, int $start, int $length = null): string {
if (extension_loaded('mbstring')) return mb_substr($str, $start, $length, 'UTF-8');
// 簡易 UTF-8 substr 實現...
}
}
strrev/strlen/substr。正確的字串反轉不只是算法題,更是國際化軟體品質的基石。選擇適合專案國際化程度的方案,並建立統一工具類,能避免無數「為什麼 Emoji 反轉變亂碼」的深夜除錯。