比较discuz和ecshop的截取字符串函数php版

云雪酱_5585

云雪酱_5585

2016-06-13

1100人浏览

原创

下面先给出两个版本函数的源代码以及简单测试,最后我会给出一个实用性更强的字符串截取函数。需要注意的是:这里讨论的字符串截取问题都是针对UTF-8编码的中文字符串。
discuz版本

复制代码 代码如下:


/**
* [discuz] 基于PHP没有安装 mb_substr 等扩展截取字符串,如果截取中文字则按2个字符计算
* @param $string 要截取的字符串
* @param $length 要截取的字符数
* @param $dot 替换截掉部分的结尾字符串
* @return 返回截取后的字符串
*/
function cutstr($string, $length, $dot = '...') {
// 如果字符串小于要截取的长度则直接返回
// 此处使用strlen获取字符串长度有很大的弊病,比如对字符串“新年快乐”要截取4个中文字符,
// 那么必须知道这4个中文字符的字节数,否则返回的字符串可能会是“新年快乐...”
if (strlen($string) return $string;
}
// 转换原字符串中htmlspecialchars
$pre = chr(1);
$end = chr(1);
$string = str_replace ( array ('&', '"', '' ), array ($pre . '&' . $end, $pre . '"' . $end, $pre . '' . $end ), $string );
$strcut = ''; // 初始化返回值
// 如果是utf-8编码(这个判断有点不全,有可能是utf8)
if (strtolower ( CHARSET ) == 'utf-8') {
// 初始连续循环指针$n,最后一个字位数$tn,截取的字符数$noc
$n = $tn = $noc = 0;
while ( $n $t = ord ( $string [$n] );
if ($t == 9 || $t == 10 || (32 // 如果是英语半角符号等,$n指针后移1位,$tn最后字是1位
$tn = 1;
$n++;
$noc++;
} elseif (194 // 如果是二字节字符$n指针后移2位,$tn最后字是2位
$tn = 2;
$n += 2;
$noc += 2;
} elseif (224 // 如果是三字节(可以理解为中字词),$n后移3位,$tn最后字是3位
$tn = 3;
$n += 3;
$noc += 2;
} elseif (240 $tn = 4;
$n += 4;
$noc += 2;
} elseif (248 $tn = 5;
$n += 5;
$noc += 2;
} elseif ($t == 252 || $t == 253) {
$tn = 6;
$n += 6;
$noc += 2;
} else {
$n++;
}
// 超过了要取的数就跳出连续循环
if ($noc >= $length) {
break;
}
}
// 这个地方是把最后一个字去掉,以备加$dot
if ($noc > $length) {
$n -= $tn;
}
$strcut = substr ( $string, 0, $n );
} else {
// 并非utf-8编码的全角就后移2位
for ($i = 0; $i $strcut .= ord ( $string [$i] ) > 127 ? $string [$i] . $string [++ $i] : $string [$i];
}
}
// 再还原最初的htmlspecialchars
$strcut = str_replace( array ($pre . '&' . $end, $pre . '"' . $end, $pre . '' . $end ), array ('&', '"', '' ), $strcut );
$pos = strrpos ( $strcut, chr ( 1 ) );
if ($pos !== false) {
$strcut = substr ( $strcut, 0, $pos );
}
return $strcut . $dot; // 最后把截取加上$dot输出
}


discuz版本的最大缺陷在于使用 strlen 获取原始字符串的长度,并用来和传入的要截取长度参数(字节数)进行比较,由于utf-8的中文字符的字节数是不固定的,所以就会面临这样的窘境:如果要截取4个中文字符应该指定多大的截取长度呢?8字节还是12字节呢?。。。这是无法预计的,也正是因为这个问题discuz的cutstr实际是有bug的,通过下面的测试结果能看出:

复制代码 代码如下:


$str1 = "欲穷千里目";
echo my_cutstr($str1, 10, "...")."\n"; // 输出:欲穷千里目... [这是一个bug,想想是什么原因导致?]
echo my_cutstr($str1, 15, "...")."\n"; // 输出:欲穷千里目


导致上述bug的原因在与cutstr函数在截取字符的时候是将一个中文字按2个字符算,那么5个中文字就是10字符,而原始字符串的长度是15字节,所以cutstr认为“成功地”从15字符的串上截取了10个字符,然后加上了“尾巴”。要解决这个bug只要在判断一下返回的子串是否和原始串相同,如果相同就不加“尾巴”。
ecshop版

复制代码 代码如下:


/**
* [ecshop] 基于PHP的 mb_substr,iconv_substr 这两个扩展来截取字符串,中文字符都是按1个字符长度计算;
* 该函数仅适用于utf-8编码的中文字符串。
*
* @param $str 原始字符串
* @param $length 截取的字符数
* @param $append 替换截掉部分的结尾字符串
* @return 返回截取后的字符串
*/
function sub_str($str, $length = 0, $append = '...') {
$str = trim($str);
$strlength = strlen($str);
if ($length == 0 || $length >= $strlength) {
return $str;
} elseif ($length $length = $strlength + $length;
if ($length $length = $strlength;
}
}
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 = trim_right(substr($str, 0, $length));
$newstr = substr($str, 0, $length);
}
if ($append && $str != $newstr) {
$newstr .= $append;
}
return $newstr;
}


ecshop版的特点和缺点都在于将中文字符算作一个字符,如果原始字符串中不含中文,比如:abcd1234,如果本意是要截取4个中文字符或者8个英文字符,那么使用ecshop的版本就得不到期望的结果,返回值的是:abcd。下面是简单的测试结果:

复制代码 代码如下:


$str1 = "白日依山尽,黄河入海流";
echo $str1."\n";
echo my_sub_str($str1, 4, "...")."\n"; // 输出:白日依山...
$str2 = "白1日2依3山4";
echo $str2."\n";
echo my_sub_str($str2, 4, "...")."\n"; // 输出:白1日2...


优化版

截取中文字符串的大部分应用场景是“原始字符串可以是中文、英文、数字混杂的,中文字按2个字符算,英文数字按1个字符算”,针对这个需求下面给出一个实现版本:

btpanel phpsite 宝塔面板PHP网站
btpanel phpsite 宝塔面板PHP网站

宝塔面板 PHP 网站管理:站点创建、删除、启停、PHP 版本切换、域名管理、SSL证书管理、伪静态管理、数据库管理

下载

复制代码 代码如下:


/**
* 字符串截取,中文字符按2个字符计算,同时支持GBK和UTF-8编码
* @param $string 要截取的字符串
* @param $length 要截取的字符数
* @param $append 添加到子串后的尾巴
* @return 返回截取后的字符串
*/
function substring($string, $length, $append = false) {
if ( $length return '';
}
// 检测原始字符串是否为UTF-8编码
$is_utf8 = false;
$str1 = @iconv("UTF-8", "GBK", $string);
$str2 = @iconv("GBK", "UTF-8", $str1);
if ( $string == $str2 ) {
$is_utf8 = true;
// 如果是UTF-8编码,则使用GBK编码的
$string = $str1;
}
$newstr = '';
for ($i = 0; $i $newstr .= ord ($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
}
if ( $is_utf8 ) {
$newstr = @iconv("GBK", "UTF-8", $newstr);
}
if ($append && $newstr != $string) {
$newstr .= $append;
}
return $newstr;
}


测试结果见下(GBK和UTF-8的结果一致):

复制代码 代码如下:


$str1 = "白日依山尽,黄河入海流";
echo substring($str1, 4, "...")."\n"; // 输出:白日...
echo substring($str1, 5, "...")."\n"; // 输出:白日依...
$str2 = "12白34日56依78山";
echo substring($str2, 4, "...")."\n"; // 输出:12白...
echo substring($str2, 5, "...")."\n"; // 输出:12白3...


作者:edwardlost' blog

php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

discuz php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

2026.11.12

80

15

Go-Zero安装与goctl环境配置教程
Go-Zero安装与goctl环境配置教程

本专题围绕Go-Zero的安装与工具链配置展开,讲清Go版本要求、goctl安装方式、代理镜像、protoc依赖与环境变量设置,说明多版本共存、升级回退与CI流水线安装的做法,并整理安装阶段的常见报错与验证步骤。

2026.11.12

60

15

Go-Zero零基础入门教程
Go-Zero零基础入门教程

本专题整理Go-Zero的入门知识,从框架定位、环境准备到跑通第一个API服务,讲清目录分层、配置加载、日志与中间件的基本用法,并对比Gin、Kratos的差异,帮助Go开发者建立整体认知,判断它是否适合当前项目。

2026.11.12

40

15

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

2026.11.06

140

12

Postman请求头Headers怎么设置
Postman请求头Headers怎么设置

围绕Postman HTTP请求头配置展开,讲解Headers选项的具体使用方式,包括Key和Value参数填写、请求头信息添加、常见Header字段设置及请求头修改方法,帮助用户解决接口调试过程中遇到的请求头配置问题。

2026.11.06

120

13

Postman接口请求发送教程
Postman接口请求发送教程

详细介绍Postman接口请求的创建与发送方法,涵盖GET和POST请求、URL配置、请求参数、Headers请求头、Body请求体设置以及响应结果查看,帮助用户掌握Postman接口请求的完整操作流程。

2026.11.06

120

10

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

2026.11.05

100

12

DBeaver安全防护教程大全
DBeaver安全防护教程大全

DBeaver安全防护专题,围绕数据库连接安全、账号密码保护和访问权限管理展开,整理SSL/TLS加密连接、SSH隧道、数据库凭据保存及证书验证等配置方法,同时介绍敏感信息保护、远程连接风险和高权限账号使用等注意事项,帮助用户提高数据库日常管理的安全性。

2026.11.05

80

12

DBeaver连接创建与配置教程
DBeaver连接创建与配置教程

本专题聚焦DBeaver数据库连接配置与管理,覆盖从新建连接、选择数据库类型到填写连接参数和验证连接的完整流程。针对MySQL、PostgreSQL、SQL Server等常见数据库,整理相应的连接设置方法,并提供驱动缺失、认证失败和连接异常等问题的排查方向。

2026.11.04

100

16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
php初学者入门课程
php初学者入门课程

共10课时 | 1万人学习

QQ音乐客户端实战开发(electron)
QQ音乐客户端实战开发(electron)

共27课时 | 9.1万人学习

JavaScript学习指南
JavaScript学习指南

共63课时 | 21.5万人学习