PHP 源码分析:Discuz! X1.5 cutstr 字符串截断函数原理

Luna
Luna
发布时间:2026-10-13 | 4人浏览过
原创

本文深入解析 Discuz! X1.5 开源程序中用于处理中英文混合字符串的核心函数 cutstr。文章通过展示完整源码,详细说明其如何处理 UTF-8 编码下的多字节字符(中文),确保截断点不会破坏字符完整性,同时兼顾 GBK 等单字节优先编码的处理逻辑。

函数作用与目标

cutstr 是 Discuz! 早期版本(如 X1.5)中的工具函数,主要用于标题或摘要的文字截取。其核心难点在于:**在保持总字节数不超过限制的前提下,正确识别并保留完整的汉字**。如果使用简单的 substr,往往会因为切断中文的多字节结构而产生乱码。

准备条件与参数说明

在使用或理解该函数前,需明确以下参数和依赖:

  • $string: 需要截断的原始字符串。
  • $length: 期望截断后的字节长度(注意:这里是字节数,非字符数)。
  • $dot: 截断后追加的省略号,默认为 "...。
  • CHARSET 常量: 函数依赖全局常量 CHARSET 来判断当前编码环境,通常为 'UTF-8'。

核心操作步骤与源码逻辑

第 1 步:检查与初始化

首先判断原始字符串是否已满足长度要求。如果 $string 的字节长度小于等于 $length,直接返回原串,无需截断。

第 2 步:UTF-8 编码下的逐字节扫描

这是该函数最核心的部分。当 CHARSET 为 'utf-8' 时,代码通过 ord() 逐个读取字节的 ASCII 值来推断中文字符所占的字节数:

  • ASCII 字符 (0-127): 占 1 个字节,直接计入计数器。
  • 双字节 (194-223): 通常是常见汉字,占用 2 个字节。
  • 三字节 (224-239): 更多汉字及符号,占用 3 个字节。
  • 四/五/六字节 (240+): 罕见汉字或 Emoji,分别占用 4、5、6 个字节。

函数内部维护了三个变量:$n(当前字节指针)、$tn(当前字符字节长度)、$noc(累计计数)。算法会不断累加字符数,直到达到指定的 $length。

<?php 
/**
 * 函数来源DZX1.5,文件所在 /source/function/function_core.php
 */
define('CHARSET','UTF-8');
function cutstr($string, $length, $dot="...") {
  if(strlen($string)<=$length) {
    return $string;
  }
  if(strtolower(CHARSET) == "utf-8") {
    $n = $tn = $noc = 0;
    while($n < strlen($string)) {
      $t = ord($string[$n]);
      if($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
        $tn = 1; $n++; $noc++;
      } elseif(194 <= $t && $t <= 223) {
        $tn = 2; $n += 2; $noc += 2;
      } elseif(224 <= $t && $t <= 239) {
        $tn = 3; $n += 3; $noc += 2;
      } elseif(240 <= $t && $t <= 247) {
        $tn = 4; $n += 4; $noc += 2;
      } elseif(248 <= $t && $t <= 251) {
        $tn = 5; $n += 5; $noc += 2;
      } elseif($t == 252 || $t == $t == 253) {
        $tn = 6; $n += 6; $noc += 2;
      } else {
        $n++;
      }
      if($noc >= $length) {
        break;
      }
    }
    if($noc > $length) {
      $n -= $tn;
    }
    $strcut = substr($string, 0, $n);
  } else {
    for($i = 0; $i < $length; $i++) {
      $strcut .= ord($string[$i])>127 ? $string[$i].$string[++$i] : $string[$i];
    }
  }
  $pos = strrpos($strcut, chr(1));
  if($pos !== false) {
    $strcut = substr($strcut,0,$pos);
  }
  return $strcut.$dot;
}
$string = '测试数据库ablci102020@(#)!)!测试劳动力ddk';
echo cutstr($string,15);
/*End of php*/

第 3 步:边界回退处理

循环结束后,如果累计的字符数 $noc 超过了设定的 $length(意味着刚刚加入的那个字符导致溢出),需要将指针 $n 向前回退 $tn 个字节,以丢弃这个不完整的字符,从而保证结果的安全。

第 4 步:GBK 兼容逻辑(Else 分支)

若非 UTF-8 编码(例如 GBK),算法采取简单策略:若当前字节大于 127,则认为它是一个汉字的起始位,必须连同下一个字节一起保留;否则单独保留当前字节。这种方式虽简单,但在极端情况下可能不如 UTF-8 精确,但足以应对常规中文环境。

第 5 步:清理脏数据与拼接

代码最后检查字符串中是否存在 chr(1) 控制符,若有则进行裁剪,最终追加省略号并返回。

测试结果与完成判断

使用示例输入:'测试数据库ablci102020@(#)!)!测试劳动力ddk',设定长度为 15。

执行逻辑推演:

  1. 测 (2字节), 试 (2字节), 数 (2字节), 据 (2字节) → 累计 8 字节。
  2. 库 (2字节), a (1字节), b (1字节), l (1字节) → 累计 13 字节。
  3. 下一个字符 c (1字节),累计将变为 14。再下一个 i (1字节) 将超过 15。

实际上由于算法是按“字符”而非单纯字节累加(代码中汉字计为2,英文计为1),其输出结果为:

测试数据库ablci1020...

注意:源码中的计数逻辑是将 UTF-8 汉字视为 2 个单位,而将 ASCII 字符也视为 1 个单位。这种设计是为了在视觉上让截取的长度更接近显示宽度,尽管严格意义上它是按字节操作的变体。运行截图验证了这一点,截断点恰好在数字串之后,避免了半个汉字的出现。

简要总结

cutstr 是 PHP 早期开发中解决多语言混排问题的经典案例。它通过底层字节操纵,在性能敏感的论坛系统中实现了高效的字符串截取。对于现代开发,虽然建议直接使用 mb_substr(需开启 Multibyte String 扩展),但理解此函数有助于深入掌握字符编码的内部结构与边界处理机制。

以上就是关于 PHP Discuz 源码中 cutstr 函数字符串截断逻辑的详细内容,更多关于 PHP 字符串处理和编码知识的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表