PHP 內建 substr 按位元組截取,對 UTF-8 中文 (3 位元組/字) 會造成截半個字元產生亂碼。mb_substr 雖支援多位元組,但早期版本需安裝 mbstring 擴充套件且效能較差。本文提供一個不依賴擴充套件、純 PHP 實作的中英混排安全截取函數 z_substr。
[php]
/**
* 多位元組安全截取函數,防止中文亂碼
*
* @param string $sourcestr 來源字串
* @param int $i 起始位元組索引 (預設 0)
* @param int $cutlength 截取長度 (以「字元寬度」計:中文=1, 英文=0.5)
* @param string $endstr 超過長度時的尾端字串 (預設空)
* @return string 截取後字串
*/
function z_substr($sourcestr = '', $i = 0, $cutlength = 150, $endstr = '')
{
$str_length = strlen($sourcestr); // 總位元組數
$returnstr = '';
$n = 0; // 已截取的「字元寬度」計數
while ($n < $cutlength && $i <= $str_length) {
$temp_str = substr($sourcestr, $i, 1); // 取 1 位元組
$ascnum = ord($temp_str); // 取 ASCII 值
if ($ascnum >= 224) {
// UTF-8 三位元組字元 (中文、日文等):1110xxxx 10xxxxxx 10xxxxxx
$returnstr .= substr($sourcestr, $i, 3);
$i += 3;
$n++; // 中文算 1 寬度
} elseif ($ascnum >= 192) {
// UTF-8 二位元組字元 (歐文擴展):110xxxxx 10xxxxxx
$returnstr .= substr($sourcestr, $i, 2);
$i += 2;
$n++; // 算 1 寬度
} else {
// ASCII 單位元組字元 (英文、數字、符號)
$returnstr .= substr($sourcestr, $i, 1);
$i += 1;
$n += 0.5; // 英文算 0.5 寬度 (兩個英文寬度約等一個中文)
}
}
// 若未截完整串,加上尾端字串 (如 "...")
if ($i < $str_length) {
$returnstr .= $endstr;
}
return $returnstr;
}
| UTF-8 位元組模式 | 首位元組範圍 | ord() 值範圍 | 處理方式 |
|---|---|---|---|
| 1 位元組 (ASCII) | 0xxxxxxx | 0 - 127 | 直接取 1 位元組,寬度 +0.5 |
| 2 位元組 | 110xxxxx | 192 - 223 | 取 2 位元組,寬度 +1 |
| 3 位元組 (中文) | 1110xxxx | 224 - 239 | 取 3 位元組,寬度 +1 |
| 4 位元組 (Emoji 等) | 11110xxx | 240 - 247 | 本函數未處理,會誤判 |
函數透過檢查首位元組 ord() 值判斷字元佔用位元組數,按完整字元單位複製,避免截半字元。
[php]
$str = "PHP中文截取測試abcdefg";
// 截取寬度 10 (約 5 個中文或 10 個英文)
echo z_substr($str, 0, 10, '...');
// 輸出:PHP中文截取...
// 從位元組索引 6 開始 (跳過 "PHP中文" 9 位元組)
echo z_substr($str, 9, 8, '..');
// 輸出:截取測試ab..
mbstring、iconv 擴充套件。mb_substr($str, 0, 10, 'UTF-8'),原生、標準、支援完整 Unicode。[php]
// PHP 5.4+ / PHP 7/8 推薦寫法
function safe_substr($string, $start, $length, $encoding = 'UTF-8') {
if (extension_loaded('mbstring')) {
return mb_substr($string, $start, $length, $encoding);
}
// 降級相容:無 mbstring 時使用 z_substr 邏輯
return z_substr($string, $start, $length);
}
若專案仍在 PHP 5.2/5.3 且無法安裝擴充套件,z_substr 是不錯的備選方案;否則請優先使用 mb_substr。