PHP字符串截取函数对比:Discuz、Ecshop及优化版实现
在Web开发中,UTF-8编码下的字符串截断是常见需求。不同框架对中英文长度的定义不同,直接使用原生函数容易导致截断不完整或显示异常。本文通过对比Discuz和Ecshop的实现逻辑,提供兼顾性能与兼容性的优化方案。
结果与目标
在处理包含中文、英文混合的长文本时,往往需要将内容截取为固定长度并添加省略号。核心难点在于确定“长度”的定义:是按字节数计算,还是按视觉字符数计算?尤其是当需要让一个中文字符占据两个位置(类似GBK编码习惯)时,算法实现变得复杂。以下将对比两种主流方案及其潜在问题。
准备条件
- 编码环境:主要讨论UTF-8编码,这是目前网页的标准编码格式。
- 应用场景:列表页标题截断、简介生成等需控制显示宽度的场景。
- 核心痛点:原生
substr()在中文字符边界截断会产生乱码;纯字节计数无法直观对应“字数”。
真实操作顺序
第1步:理解Discuz版本的实现逻辑
目的:在不依赖扩展库的情况下,手动遍历字符串以模拟长度计算。 原理:该版本试图通过逐字节判断ASCII码范围来识别字符类型。其关键策略是将中文字符计为2个单位,英文字母计为1个单位。
代码特点:
- 使用
ord()获取字节值,通过位运算判断字符类型(如$t == 9 || $t == 10判断换行)。 - 通过
$pre = chr(1)等占位符保护HTML实体字符,防止被错误截断。 - 缺陷分析:
strlen()获取的是字节总长,但与逻辑计数器$noc(累计宽度)进行直接比较存在逻辑偏差。例如5个中文汉字,字节长度为15,但逻辑宽度为10。若请求截取宽度10,函数可能在内部逻辑溢出后仍追加省略号,导致显示重复或多余字符。
/**
* Discuz风格截取:中文字符按2个字符宽度计算
* @param string $string 源字符串
* @param int $length 目标逻辑长度
* @param string $dot 尾部填充
*/
function cutstr($string, $length, $dot = '...') {
if (strlen($string) <= $length) return $string;
// 简单的HTML实体转义保护逻辑(伪代码示意)
// ... 替换 & lt; gt; for special chars ...
$n = $tn = $noc = 0;
while ($n < strlen($string)) {
$t = ord($string[$n]);
// 英文/数字/符号
if ($t == 9 || $t == 10 || ($t >= 32 && $t <= 126)) { $tn = 1; $n++; $noc++;
// 双字节(通常用于Latin extended或其他)
} elseif ($t >= 194 && $t <= 223) { $tn = 2; $n += 2; $noc += 2;
// 三字节 (UTF-8 CJK)
} elseif ($t >= 224 && $t <= 239) { $tn = 3; $n += 3; $noc += 2;
// 四字节及以上,按2计
} else { $tn = ($t >= 240 ? 4 : 3); $n += $tn; $noc += 2; }
if ($noc >= $length) break;
}
if ($noc > $length) $n -= $tn;
$strcut = substr($string, 0, $n);
// 还原HTML实体
// ... 反向替换 ...
return $strcut . $dot;
}
第2步:理解Ecshop版本的实现逻辑
目的:利用PHP内置的多字节处理函数,简化代码并确保安全性。
原理:优先尝试mb_substr,若无则降级至iconv_substr。此版本严格按照字符数量截取,即“中文字算1,英文字母算1”。
优点:代码简洁,不易出错。 缺点:不符合传统排版中“全角/半角”混排的习惯。如果前端CSS设定一个中文占位宽度等于两个英文宽度,该函数会导致截断长度偏短(视觉上只截取了一半的中文空间)。
function sub_str($str, $length = 0, $append = '...') {
$str = trim($str);
$strlength = strlen($str);
if ($length == 0 || $length >= $strlength) return $str;
// 核心:使用扩展库精确截取字符
if (function_exists('mb_substr')) {
$newstr = mb_substr($str, 0, $length, 'utf-8');
} elseif (function_exists('iconv_substr')) {
$newstr = iconv_substr($str, 0, $length, 'utf-8');
} else {
// 无扩展库时的不安全回退
$newstr = substr($str, 0, $length);
}
if ($append && $str != $newstr) {
$newstr .= $append;
}
return $newstr;
}
第3步:应用优化版函数(GBK转换法)
目的:解决“中文宽、英文窄”且无扩展库限制的问题。
原理:利用PHP的iconv扩展,临时将UTF-8字符串转为GBK编码。在GBK中,所有中文字符均为双字节,英文单字节。此时直接使用基于字节的循环即可实现“中文算2位,英文算1位”,最后再转回UTF-8。
优势:不依赖mbstring扩展(仅依赖iconv),算法逻辑清晰,性能优于手动正则匹配。
/**
* 通用优化版:支持UTF-8,中文按2字符算
* @param string $string
* @param int $length
* @param bool|string $append
*/
function substring($string, $length, $append = false) {
if ($length <= 0) return '';
// 检测并转换为GBK以便统一字节计数
$is_utf8 = true;
$str_gbk = @iconv("UTF-8", "GBK", $string);
$back_to_utf = @iconv("GBK", "UTF-8", $str_gbk);
if ($string != $back_to_utf) {
// 非UTF-8或非标准编码,简单处理
return substr($string, 0, $length) . ($append ? '...' : '');
}
// 此时使用GBK字符串进行截取
$string = $str_gbk;
$newstr = '';
for ($i = 0; $i < $length; $i++) {
// 如果当前字节最高位为1(属于双字节字符的一部分)
if (ord($string[$i]) > 127) {
$newstr .= $string[$i] . $string[++$i]; // 跳过下一字节
} else {
$newstr .= $string[$i];
}
}
// 转回UTF-8
if ($is_utf8) {
$newstr = iconv("GBK", "UTF-8", $newstr);
}
if ($append && $newstr != $string) {
$newstr .= $append;
}
return $newstr;
}
测试验证:
- 输入“白日依山尽”,截取4位:输出“白日...”(2个中文=4位)。符合预期。
- 输入“12白34日”,截取4位:输出“12白...”(2个英文+1个中文=4位)。符合预期。
完成判断
通过对比发现,Ecshop方案适合严格字数统计场景,Discuz方案适合视觉宽度适配但代码冗余,而优化版脚本利用编码转换技巧,在无需mbstring扩展的情况下,实现了精准的“中文双倍宽”截断效果,是目前中小型项目中较优的解决方案。
简要总结
以上就是PHP中字符串截取函数的对比分析与优化的详细内容。在处理多语言混合内容时,选择正确的截取算法能有效避免乱码和排版错乱。更多关于PHP字符串操作的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









