PHP 內建 strlen()、substr() 按位元組操作。GB2312/Big5 中文字佔 2 位元組,UTF-8 中文字佔 3 位元組。直接按字元數截取極易將雙位元組字元切半,產生亂碼。
$str = "這是1個字符串"; // 含半形數字
if (strlen($str) > 10) $str = substr($str, 10) . "...";
// 第 10、11 位元組剛好切斷「符」字 → 亂碼
核心邏輯:遍歷字串,檢測當前位元組是否為多位元組字元首位元組(ord($char) > 127 或 > 0x80),若是則一次取 2(GBK)或 3(UTF-8)位元組,並調整迴圈索引。
cutstr()function cutstr($string, $length, $dot = '...') {
$strcut = '';
for ($i = 0; $i < $length - strlen($dot) - 1; $i++) {
$strcut .= (ord($string[$i]) > 127)
? $string[$i] . $string[++$i] // 雙位元組字元
: $string[$i]; // 單位元組字元
}
return $strcut . $dot;
}
cutTitle()(含長度校驗)function cutTitle($str, $len, $tail = "") {
$length = strlen($str);
$lentail = strlen($tail);
$result = "";
if ($length > $len) {
$len -= $lentail;
for ($i = 0; $i < $len; $i++) {
if (ord($str[$i]) < 127) {
$result .= $str[$i];
} else {
$result .= $str[$i] . $str[++$i];
}
}
$result = (strlen($result) > $len)
? substr($result, 0, -2) . $tail
: $result . $tail;
} else {
$result = $str;
}
return $result;
}
mysubstr($str, $start, $len)function mysubstr($str, $start, $len) {
$tmpstr = "";
$strlen = $start + $len;
for ($i = 0; $i < $strlen; $i++) {
if (ord(substr($str, $i, 1)) > 0xA0) { // GBK 範圍
$tmpstr .= substr($str, $i, 2);
$i++;
} else {
$tmpstr .= substr($str, $i, 1);
}
}
return $tmpstr;
}
utf8Substr()function utf8Substr($str, $from, $len) {
return preg_replace(
'#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){0,'.$from.'}'.
'((?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){0,'.$len.'}).*#s',
'$1', $str);
}
利用 UTF-8 位元組特徵(首位元組 0xC0-0xFF,續位元組 0x80-0xBF)精準匹配字元。
cut_str()function cut_str($string, $sublen, $start = 0, $code = 'UTF-8') {
if ($code == 'UTF-8') {
$pa = "/[\x01-\x7F]|[\xC0-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/";
preg_match_all($pa, $string, $t_string);
$slice = array_slice($t_string[0], $start, $sublen);
return (count($t_string[0]) - $start > $sublen)
? join('', $slice) . "..."
: join('', $slice);
} else { // GB2312
$start *= 2; $sublen *= 2;
$strlen = strlen($string);
$tmpstr = '';
for ($i = 0; $i < $strlen; $i++) {
if ($i >= $start && $i < $start + $sublen) {
$tmpstr .= (ord(substr($string, $i, 1)) > 129)
? substr($string, $i, 2) : substr($string, $i, 1);
}
if (ord(substr($string, $i, 1)) > 129) $i++;
}
return (strlen($tmpstr) < $strlen) ? $tmpstr . "..." : $tmpstr;
}
}
sysSubStr()function sysSubStr($String, $Length, $Append = false) {
if (strlen($String) <= $Length) return $String;
$I = 0; $StringLast = [];
while ($I < $Length) {
$StringTMP = substr($String, $I, 1);
$ord = ord($StringTMP);
if ($ord >= 224) { $StringTMP = substr($String, $I, 3); $I += 3; } // UTF-8 3 bytes
elseif ($ord >= 192) { $StringTMP = substr($String, $I, 2); $I += 2; } // UTF-8 2 bytes / GBK
else { $I++; }
$StringLast[] = $StringTMP;
}
$result = implode("", $StringLast);
return $Append ? $result . "..." : $result;
}
強烈建議使用 mbstring 擴展提供的多位元組安全函數:
mb_internal_encoding("UTF-8"); // 或 GBK
$sub = mb_substr($str, 0, 10, "UTF-8");
$len = mb_strlen($str, "UTF-8");
mb_substr/mb_strlen。cut_str 雙模式函數。SET NAMES utf8(或 gbk),確保存取編碼一致。<meta charset="UTF-8">。