首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 中文字串截取函式 cnSubStr:雙位元組編碼 (GBK/Big5) 環境下的經典實作與現代 mb_substr 替代

     編輯:PHP 相關知識

歷史背景

PHP 4/5 早期無原生多位元組支援,中文編碼 (GBK/Big5/UTF-8) 皆為「位元組串」。直接用 substr() 會在雙位元組字元中間截斷,產生亂碼。cnSubStr 透過檢測 ord($char) >= 128 判斷雙位元組首位元組,一次複製兩個位元組,實現「按字元截取」。

原始實作分析

<?php
function cnSubStr($string, $sublen) {
    if ($sublen >= strlen($string)) return $string;
    
    $s = '';
    for ($i = 0; $i < $sublen; $i++) {
        if (ord($string[$i]) > 127) { // 非 ASCII,視為雙位元組首位元組
            $s .= $string[$i] . $string[++$i]; // 複製兩位元組,索引+1
        } else {
            $s .= $string[$i]; // ASCII 單位元組
        }
    }
    return $s;
}

適用編碼與限制

編碼 中文位元組數 cnSubStr 適用性 說明
GBK / GB2312 / CP9362✅ 完美首位元組 0x81-0xFE,次位元組 0x40-0xFE (含 0x7F)
Big5 / CP9502✅ 完美首位元組 0x81-0xFE,次位元組 0x40-0x7E, 0xA1-0xFE
UTF-83 (中日韓)❌ 失效首位元組 0xE0-0xEF,需複製 3 位元組;ASCII 兼容但邏輯錯誤
UTF-162/4❌ 失效位元組序 (BOM)、代理對複雜

UTF-8 版本修正 (僅供舊專案維護參考)

<?php
function cnSubStr_utf8($string, $sublen) {
    if ($sublen >= mb_strlen($string, 'UTF-8')) return $string;
    // 簡單版:利用 mb_substr (需 mbstring 擴充)
    return mb_substr($string, 0, $sublen, 'UTF-8');
    
    // 純 PHP 版 (無 mbstring):
    $len = strlen($string);
    $count = 0;
    $result = '';
    for ($i = 0; $i < $len && $count < $sublen; $i++) {
        $byte = ord($string[$i]);
        if ($byte < 0x80) { // 1-byte ASCII
            $char_len = 1;
        } elseif (($byte & 0xE0) === 0xC0) { // 2-byte
            $char_len = 2;
        } elseif (($byte & 0xF0) === 0xE0) { // 3-byte (中日韓)
            $char_len = 3;
        } elseif (($byte & 0xF8) === 0xF0) { // 4-byte (Emoji)
            $char_len = 4;
        } else {
            $char_len = 1; // 非法位元組,單獨處理
        }
        $result .= substr($string, $i, $char_len);
        $i += $char_len - 1;
        $count++;
    }
    return $result;
}

現代最佳實踐:mbstring / Intl / grapheme

<?php
// 1. mb_substr (最常用,需 php-mbstring)
$text = "242432反對感是456犯得上廣泛大使館地方7890";
echo mb_substr($text, 0, 20, 'UTF-8'); // 反對感是456犯得上廣泛大使

// 2. grapheme_substr (PHP 7.0+ Intl 擴充,按「使用者感知字元」截取)
// 正確處理 Emoji、組合字元 (如 "e\u0301" = é 算 1 字元)
echo grapheme_substr($text, 0, 20); 

// 3. Mbstring 正則分割再組合 (極長字串效能優化)
function mb_substr_fast($str, $start, $length, $encoding = 'UTF-8') {
    // 利用 mb_strpos 定位位元組偏移,再 substr
    $byte_start = mb_strpos($str, '', $start, $encoding); // 簡化邏輯
    // 實際生產建議直接用 mb_substr,C 層實現已高度優化
    return mb_substr($str, $start, $length, $encoding);
}

// 4. 截取並加省略號
function truncate($text, $length, $suffix = '...', $encoding = 'UTF-8') {
    if (mb_strlen($text, $encoding) <= $length) return $text;
    return mb_substr($text, 0, $length, $encoding) . $suffix;
}

效能對比 (100萬次截取 20 字元)

函式 耗時 (估) 備註
cnSubStr (GBK)~0.8 秒PHP 迴圈,純位元組操作
mb_substr (UTF-8)~0.3 秒C 層實現,高度優化
grapheme_substr~0.5 秒ICU 庫,正確處理組合字元
純 PHP UTF-8 迴圈~2.5 秒PHP 層逐位元組判斷,慢

常見陷阱

  1. 資料庫 CHAR vs VARCHAR:CHAR(20) 存「中文」佔 20 位元組 (GBK) = 10 字元,非 20 字元。
  2. 前端 JS 截取:str.slice(0,20) 按 UTF-16 代碼單元,Emoji 算 2 個,需 [...str].slice(0,20).join('')。
  3. 截取後拼接:substr($a,0,10) . substr($b,0,10) 可能產生半個中文字元,存入 DB 導致查詢錯誤。
  4. 正則匹配截取:preg_match('/^.{20}/u', $str, $m) 正確但較慢。

總結

cnSubStr 是 PHP 早期雙位元組編碼 (GBK/Big5) 環境下的經典智慧結晶。現代專案統一 UTF-8、裝 mbstring/Intl、直接用 mb_substr() 或 grapheme_substr()。維護舊專案時,確認編碼一致性,必要時寫編碼感知的封裝函式。核心原則:按字元截取,而非按位元組。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved