PHP 4/5 早期無原生多位元組支援,中文編碼 (GBK/Big5/UTF-8) 皆為「位元組串」。直接用 substr() 會在雙位元組字元中間截斷,產生亂碼。cnSubStr 透過檢測 ord($char) >= 128 判斷雙位元組首位元組,一次複製兩個位元組,實現「按字元截取」。
<?php
function cnSubStr($string, $sublen) {
if ($sublen >= strlen($string)) return $string;
$s = '';
for ($i = 0; $i < $sublen; $i++) {
if (ord($string[$i]) > 127) { // 非 ASCII,視為雙位元組首位元組
$s .= $string[$i] . $string[++$i]; // 複製兩位元組,索引+1
} else {
$s .= $string[$i]; // ASCII 單位元組
}
}
return $s;
}
| 編碼 | 中文位元組數 | cnSubStr 適用性 | 說明 |
|---|---|---|---|
| GBK / GB2312 / CP936 | 2 | ✅ 完美 | 首位元組 0x81-0xFE,次位元組 0x40-0xFE (含 0x7F) |
| Big5 / CP950 | 2 | ✅ 完美 | 首位元組 0x81-0xFE,次位元組 0x40-0x7E, 0xA1-0xFE |
| UTF-8 | 3 (中日韓) | ❌ 失效 | 首位元組 0xE0-0xEF,需複製 3 位元組;ASCII 兼容但邏輯錯誤 |
| UTF-16 | 2/4 | ❌ 失效 | 位元組序 (BOM)、代理對複雜 |
<?php
function cnSubStr_utf8($string, $sublen) {
if ($sublen >= mb_strlen($string, 'UTF-8')) return $string;
// 簡單版:利用 mb_substr (需 mbstring 擴充)
return mb_substr($string, 0, $sublen, 'UTF-8');
// 純 PHP 版 (無 mbstring):
$len = strlen($string);
$count = 0;
$result = '';
for ($i = 0; $i < $len && $count < $sublen; $i++) {
$byte = ord($string[$i]);
if ($byte < 0x80) { // 1-byte ASCII
$char_len = 1;
} elseif (($byte & 0xE0) === 0xC0) { // 2-byte
$char_len = 2;
} elseif (($byte & 0xF0) === 0xE0) { // 3-byte (中日韓)
$char_len = 3;
} elseif (($byte & 0xF8) === 0xF0) { // 4-byte (Emoji)
$char_len = 4;
} else {
$char_len = 1; // 非法位元組,單獨處理
}
$result .= substr($string, $i, $char_len);
$i += $char_len - 1;
$count++;
}
return $result;
}
<?php
// 1. mb_substr (最常用,需 php-mbstring)
$text = "242432反對感是456犯得上廣泛大使館地方7890";
echo mb_substr($text, 0, 20, 'UTF-8'); // 反對感是456犯得上廣泛大使
// 2. grapheme_substr (PHP 7.0+ Intl 擴充,按「使用者感知字元」截取)
// 正確處理 Emoji、組合字元 (如 "e\u0301" = é 算 1 字元)
echo grapheme_substr($text, 0, 20);
// 3. Mbstring 正則分割再組合 (極長字串效能優化)
function mb_substr_fast($str, $start, $length, $encoding = 'UTF-8') {
// 利用 mb_strpos 定位位元組偏移,再 substr
$byte_start = mb_strpos($str, '', $start, $encoding); // 簡化邏輯
// 實際生產建議直接用 mb_substr,C 層實現已高度優化
return mb_substr($str, $start, $length, $encoding);
}
// 4. 截取並加省略號
function truncate($text, $length, $suffix = '...', $encoding = 'UTF-8') {
if (mb_strlen($text, $encoding) <= $length) return $text;
return mb_substr($text, 0, $length, $encoding) . $suffix;
}
| 函式 | 耗時 (估) | 備註 |
|---|---|---|
| cnSubStr (GBK) | ~0.8 秒 | PHP 迴圈,純位元組操作 |
| mb_substr (UTF-8) | ~0.3 秒 | C 層實現,高度優化 |
| grapheme_substr | ~0.5 秒 | ICU 庫,正確處理組合字元 |
| 純 PHP UTF-8 迴圈 | ~2.5 秒 | PHP 層逐位元組判斷,慢 |
CHAR(20) 存「中文」佔 20 位元組 (GBK) = 10 字元,非 20 字元。str.slice(0,20) 按 UTF-16 代碼單元,Emoji 算 2 個,需 [...str].slice(0,20).join('')。substr($a,0,10) . substr($b,0,10) 可能產生半個中文字元,存入 DB 導致查詢錯誤。preg_match('/^.{20}/u', $str, $m) 正確但較慢。cnSubStr 是 PHP 早期雙位元組編碼 (GBK/Big5) 環境下的經典智慧結晶。現代專案統一 UTF-8、裝 mbstring/Intl、直接用 mb_substr() 或 grapheme_substr()。維護舊專案時,確認編碼一致性,必要時寫編碼感知的封裝函式。核心原則:按字元截取,而非按位元組。