PHP 內置 substr 按字節截取,直接用於中文會導致亂碼(UTF-8 一個漢字 3 字節,GB2312 2 字節)。mb_substr 雖支持多字節,但早期版本需安裝 mbstring 擴展。本文提供一個無擴展依賴、雙編碼兼容的純 PHP 實現 substr_zh。
function substr_zh($string, $sublen, $start = 0, $code = 'UTF-8')
$string:待截取字符串$sublen:截取長度(字符數)$start:起始位置(字符索引)$code:編碼類型,'UTF-8' 或 'GB2312'(默認 UTF-8)...<?php
function substr_zh($string, $sublen, $start = 0, $code = 'UTF-8') {
if ($code == 'UTF-8') {
// 正則匹配所有合法 UTF-8 字符(含 ASCII、多字節)
$pa = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]/";
preg_match_all($pa, $string, $t_string);
$chars = $t_string[0];
if (count($chars) - $start > $sublen) {
return join('', array_slice($chars, $start, $sublen)) . "...";
}
return join('', array_slice($chars, $start, $sublen));
} else {
// GB2312/GBK:雙字節字符高位 > 129
$start *= 2; $sublen *= 2;
$strlen = strlen($string);
$tmpstr = '';
for ($i = 0; $i < $strlen; $i++) {
if ($i >= $start && $i < ($start + $sublen)) {
if (ord(substr($string, $i, 1)) > 129) {
$tmpstr .= substr($string, $i, 2);
$i++;
} else {
$tmpstr .= substr($string, $i, 1);
}
}
// 循環中已處理雙字節跳過,這裡無需額外判斷
}
if (strlen($tmpstr) < $strlen) $tmpstr .= "...";
return $tmpstr;
}
}
// 測試
$str = "頂置車頂起困境檲上盯協押畏奇才趄肯困楞右腳可愛有";
echo substr_zh($str, 10, 0, 'GB2312'); // 輸出前 10 個漢字
?>
正則 $pa 覆蓋了 UTF-8 所有合法字節序列:
[\x01-\x7f]:ASCII 字符(1 字節)[\xc2-\xdf][\x80-\xbf]:2 字節字符(基本多文種平面)\xe0[\xa0-\xbf][\x80-\xbf]:3 字節字符(起始碼點較低)[\xe1-\xef][\x80-\xbf][\x80-\xbf]:3 字節字符(常規範圍,含中日韓)\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]:4 字節字符(補充平面,Emoji 等)[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]:4 字節字符(擴展範圍)利用 ord() > 129 判斷高位字節,遇到則取兩字節並索引 $i++ 跳過低位。將字符長度換算為字節長度處理。
PHP 5.4+ 內置 mb_substr(需 mbstring)已完美解決此問題,且性能更優:
// 推薦現代寫法
echo mb_substr($str, 0, 10, 'UTF-8');
若環境確定有 mbstring,請優先使用內置函數。本函數適用於無法安裝擴展的舊環境或教學參考。