php中mb_substr,mb_substr

网上看到一篇文章 discuz和ecshop截取字符串的两个函数,比较了一下两个版本的函数,都各有局限,只能在特定的前提下使用,但是学习一下有利于拓宽思路,了解PHP的扩展功能

下面先给出两个版本函数的源代码以及简单测试,最后我会给出一个实用性更强的字符串截取函数。需要注意的是:这里讨论的字符串截取问题都是针对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) <= $length) {

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 < strlen ( $string ) ) {

$t = ord ( $string [$n] );

if ($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {

// 如果是英语半角符号等,$n指针后移1位,$tn最后字是1位

$tn = 1;

$n++;

$noc++;

} elseif (194 <= $t && $t <= 223) {

// 如果是二字节字符$n指针后移2位,$tn最后字是2位

$tn = 2;

$n += 2;

$noc += 2;

} elseif (224 <= $t && $t <= 239) {

// 如果是三字节(可以理解为中字词),$n后移3位,$tn最后字是3位

$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 == 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 < $length; $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 < 0) {

$length = $strlength + $length;

if ($length < 0) {

$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个字符算”,针对这个需求下面给出一个实现版本:

复制代码 代码如下:

/**

* 字符串截取,中文字符按2个字符计算,同时支持GBK和UTF-8编码

* @param $string 要截取的字符串

* @param $length 要截取的字符数

* @param $append 添加到子串后的尾巴

* @return 返回截取后的字符串

*/

function substring($string, $length, $append = false) {

if ( $length <= 0 ) {

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 < $length; $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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值