中文截取亂碼的主因在於多位元組編碼:UTF-8 中文字佔 3 位元組,GB2312 佔 2 位元組。若直接用 substr 按位元組截取,極易將一個中文字「切一半」,產生亂碼。本文提供三種解法,最後一種通用性最佳。
function chinesesubstr($str, $start, $len) {
$strlen = $start + $len;
for ($i = $start; $i < $strlen;) {
if (ord(substr($str, $i, 1)) > 0xa0) { // 中文
$tmpstr .= substr($str, $i, 2);
$i += 2;
} else {
$tmpstr .= substr($str, $i, 1);
$i++;
}
}
return $tmpstr;
}
function chinesesubstr($str, $start, $len) {
$strlen = $start + $len;
for ($i = $start; $i < $strlen;) {
if (ord(substr($str, $i, 1)) > 0xa0) { // 中文
$tmpstr .= substr($str, $i, 3);
$i += 3;
} else {
$tmpstr .= substr($str, $i, 1);
$i++;
}
}
return $tmpstr;
}
缺點:需知道編碼,且無法處理混合編碼。
此函式同時支援 UTF-8 與 GB2312,不需擔心編碼問題:
function cut_str($string, $sublen, $start = 0, $code = 'UTF-8') {
if ($code == 'UTF-8') {
$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);
if (count($t_string[0]) - $start > $sublen) {
return join('', array_slice($t_string[0], $start, $sublen)) . "…";
}
return join('', array_slice($t_string[0], $start, $sublen));
} else { // GB2312/GBK
$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);
} else {
$tmpstr .= substr($string, $i, 1);
}
}
if (ord(substr($string, $i, 1)) > 129) $i++;
}
if (strlen($tmpstr) < $strlen) $tmpstr .= "…";
return $tmpstr;
}
}
$str = "你好世界 Hello World";
echo cut_str($str, 6); // 輸出:你好世界…
echo cut_str($str, 6, 0, 'GB2312'); // 同樣正確
PHP 5.4+ 可直接使用 mb_substr(需啟用 mbstring 擴充):
echo mb_substr($str, 0, 6, 'UTF-8'); // 簡潔可靠
建議專案統一使用 UTF-8 並善用 mb_* 函式庫,徹底避免編碼困擾。