下面先給出兩個版本函數的原始碼以及簡單測試,最後我會給出一個實用性更強的字串截取函數。需要注意的是:這裡討論的字串截取問題都是針對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, $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, $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
http://www.bkjia.com/PHPjc/325891.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/325891.htmlTechArticle下面先給出兩個版本函數的原始碼以及簡單測試,最後我會給出一個實用性更強的字串截取函數。需要注意的是:這裡討論的字串截取...