substr() 按位元組截取,中文 UTF-8 佔 3 位元組、Emoji 佔 4 位元組,直接用會切斷字元導致亂碼。本文系統整理 mb_substr()、mb_strimwidth()、grapheme_substr()、正則過濾四大方案,比較編碼支援、效能、功能差異,提供最佳實踐建議。
$str = '中文測試'; echo substr($str, 0, 3); // '中' (切在 '文' 3 bytes 中間) echo substr($str, 0, 6); // '中文' (剛好 2 個字 * 3 bytes)
substr() 完全不知編碼,純位元組操作| 函式 | 擴展需求 | 編碼支援 | 單位 | 功能 | 效能 | 推薦場景 |
|---|---|---|---|---|---|---|
mb_substr() |
mbstring (內建) | UTF-8 等多種 | 字元 | 截取、可指定起始/長度/編碼 | 快 | ✅ 通用首選 |
mb_strimwidth() |
mbstring | UTF-8 等 | 寬度 (半形=1, 全形=2) | 截取+自動加尾碼 (如 ...) | 快 | 列表摘要、固定寬度顯示 |
grapheme_substr() |
intl (PECL) | Unicode Grapheme Cluster | 使用者感知字元 | 正確處理組合字元 (如 é = e + ´) | 中等 | Emoji、複雜文字、國際化嚴格場景 |
正則 preg_match_all('/./u') |
PCRE (內建) | UTF-8 字元 | 字元 | 可迭代、過濾、轉陣列 | 慢 | 教學、特殊邏輯、無 mbstring 環境 |
<?php
$str = '中文測試abc';
echo mb_substr($str, 0, 4, 'UTF-8'); // '中文測'
echo mb_substr($str, 2, 3, 'UTF-8'); // '測試a'
// 封裝函式
function msubstr(string $str, int $start, int $length, string $encoding = 'UTF-8'): string {
return mb_substr($str, $start, $length, $encoding);
}
<?php $str = '這是一段很長的中文標題需要截斷顯示'; echo mb_strimwidth($str, 0, 20, '...', 'UTF-8'); // '這是一段很長的中文...' (寬度 20: 中文算 2, 英文算 1) echo mb_strimwidth($str, 0, 20, '', 'UTF-8'); // 無尾碼
<?php $str = 'A👨👩👧👦B'; // 家庭 Emoji = 1 Grapheme Cluster (多碼點組合) echo strlen($str); // 25 bytes echo mb_strlen($str, 'UTF-8'); // 7 碼點 echo grapheme_strlen($str); // 3 Grapheme Clusters (A, 👨👩👧👦, B) echo grapheme_substr($str, 0, 2); // 'A👨👩👧👦' 正確! echo mb_substr($str, 0, 2, 'UTF-8'); // 'A👨' 切斷組合字元!
function utf8_substr(string $str, int $start, int $length): string {
preg_match_all('/./u', $str, $matches);
$chars = $matches[0];
return implode('', array_slice($chars, $start, $length));
}
function truncateHtml(string $html, int $maxLen, string $suffix = '...'): string {
// 簡易版:移除標籤後截取,實際專案建議用 HTMLPurifier 或 DOMDocument
$text = strip_tags($html);
if (mb_strlen($text, 'UTF-8') <= $maxLen) return $html;
return mb_strimwidth($text, 0, $maxLen, $suffix, 'UTF-8');
}
// mb_substr: ~0.15 秒 // mb_strimwidth: ~0.18 秒 // grapheme_substr: ~0.45 秒 (intl 開銷) // preg_match_all: ~2.5 秒
mb_substr($str, $start, $len, 'UTF-8'),專案統一封裝 msubstr()。mb_strimwidth($str, 0, $width, '...', 'UTF-8'),自動加尾碼、按視覺寬度。intl 擴展,用 grapheme_substr(),唯一正確 方案。LEFT(col, n) 同樣按位元組,應用層截取更可控。mb_internal_encoding('UTF-8'),省略第 4 參數。| 錯誤 | 後果 | 修正 |
|---|---|---|
substr($str, 0, 10) 中文 | 亂碼、切斷字元 | 改用 mb_substr |
mb_substr($str, 0, 10) 未指定編碼 | 預設內部編碼可能非 UTF-8 | 指定 'UTF-8' 或設定 mb_internal_encoding |
Emoji 用 mb_substr | 切斷組合字元、顯示 | 用 grapheme_substr (需 intl) |
相關專題:《PHP 字串用法總結》、《PHP 編碼與轉碼操作技巧匯總》。