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

PHP截取中文字符串亂碼問題的完美解決方案:mb_substr與自定義函數

     編輯:PHP 相關知識

PHP 中文截取亂碼根因與解決方案大全

substr() 按**字節**截取,UTF-8 編碼下一個漢字佔 3 字節,直接截取必然導致半個字符殘留,瀏覽器渲染出現 亂碼。本文提供從內置函數到自定義兼容函數的完整解決方案。

根因分析

  • GBK/GB2312:漢字 2 字節,ASCII 1 字節。substr 截在奇數位置即亂碼。
  • UTF-8:漢字 3 字節 (部分 4 字節 Emoji),ASCII 1 字節。substr 截在非 3 整數倍位置即亂碼。

方案一:mb_substr —— 官方推薦,最簡單 (需開啟 mbstring 擴展)

<?php
// 確保腳本編碼與聲明一致
declare(encoding='UTF-8'); // PHP 7.1+ 或文件保存為 UTF-8 無 BOM
mb_internal_encoding('UTF-8'); // 設置內部編碼

$str = "你好世界,Hello World!";
// 截取前 10 個字符 (含中英文)
$sub = mb_substr($str, 0, 10, 'UTF-8'); // 結果: "你好世界,Hello"
echo $sub;
?>

✅ **優點**:原生支持多字節、支持起始位置負數 (從末尾算起)、性能好。
⚠️ **注意**:生產環境務必在 `php.ini` 設置 `mbstring.internal_encoding = UTF-8` 或入口文件調用 `mb_internal_encoding('UTF-8')`。

方案二:mb_strimwidth —— 截取並自動添加省略號

<?php
$str = "這是一段很長的中文標題需要截取顯示";
// 寬度按字符寬度計算:中文算 2,英文算 1 (近似視覺寬度)
// 第 4 參數為省略標記,第 5 參數編碼
echo mb_strimwidth($str, 0, 20, '...', 'UTF-8'); 
// 輸出: "這是一段很長的中文..."
?>

方案三:自定義 UTF-8 安全截取函數 (無 mbstring 環境兜底)

<?php
/**
 * 純 PHP 實現 UTF-8 字符串截取,不依賴 mbstring
 * @param string $str 字符串
 * @param int $start 起始字符位置 (0 起)
 * @param int $length 截取字符數
 * @return string
 */
function utf8_substr($str, $start, $length) {
    if ($length <= 0) return '';
    $bytes = strlen($str);
    $chars = 0;
    $startByte = 0;
    $endByte = $bytes;

    // 1. 定位起始字節位置
    for ($i = 0; $i < $bytes && $chars < $start; $i++) {
        $c = ord($str[$i]);
        if ($c < 0x80) $i += 0;      // 1 byte (ASCII)
        elseif ($c < 0xE0) $i += 1;  // 2 bytes
        elseif ($c < 0xF0) $i += 2;  // 3 bytes
        else $i += 3;                // 4 bytes (Emoji)
        $chars++;
    }
    $startByte = $i;
    if ($chars < $start) return ''; // 起始位置超出長度

    // 2. 定位結束字節位置
    $chars = 0;
    for ($i = $startByte; $i < $bytes && $chars < $length; $i++) {
        $c = ord($str[$i]);
        if ($c < 0x80) $i += 0;
        elseif ($c < 0xE0) $i += 1;
        elseif ($c < 0xF0) $i += 2;
        else $i += 3;
        $chars++;
    }
    $endByte = $i;

    return substr($str, $startByte, $endByte - $startByte);
}

// 測試
$str = "😀你好abc世界👍";
echo utf8_substr($str, 1, 4); // 輸出: "你好ab" (Emoji 算 1 字符)
?>

方案四:兼容 GBK/UTF-8 雙編碼的通用截取類 (舊項目維護)

<?php
class StringHelper {
    public static function cut($str, $length, $charset = 'UTF-8', $suffix = '...') {
        $charset = strtoupper($charset);
        if ($charset === 'UTF-8') {
            if (function_exists('mb_substr')) {
                $cut = mb_substr($str, 0, $length, 'UTF-8');
            } else {
                $cut = self::utf8Substr($str, 0, $length);
            }
        } elseif ($charset === 'GBK' || $charset === 'GB2312') {
            // GBK: 逐字節判斷高位位元
            $cut = '';
            for ($i = 0, $len = strlen($str); $i < $len && mb_strlen($cut, 'GBK') < $length; $i++) {
                if (ord($str[$i]) > 0x80) {
                    $cut .= $str[$i] . $str[++$i];
                } else {
                    $cut .= $str[$i];
                }
            }
        } else {
            $cut = substr($str, 0, $length);
        }
        // 只有真正截斷了才加後綴
        if (mb_strlen($cut, $charset) < mb_strlen($str, $charset)) {
            $cut .= $suffix;
        }
        return $cut;
    }
    // ... 內部 utf8Substr 實現同上
}

// 使用
echo StringHelper::cut($gbkStr, 20, 'GBK');
echo StringHelper::cut($utf8Str, 20, 'UTF-8');
?>

常見業務場景封裝

<?php
// 1. 文章摘要生成 (去標籤 + 截取)
function makeExcerpt($html, $length = 200) {
    $text = strip_tags($html);
    $text = preg_replace('/\s+/', ' ', $text); // 合併空白
    return StringHelper::cut($text, $length, 'UTF-8');
}

// 2. 列表標題截取 (保證不破壞 HTML 結構 - 需配合 HTML 截取庫如 htmLawed)
// 簡易版:純文本標題
function cutTitle($title, $len = 30) {
    return StringHelper::cut($title, $len, 'UTF-8');
}
?>

總結建議

  1. 現代項目 (PHP 7+):強制啟用 `mbstring`,統一使用 `mb_substr` / `mb_strimwidth`,入口文件 `mb_internal_encoding('UTF-8')`。
  2. 舊項目無 mbstring:引入上文 `utf8_substr` 純 PHP 實現。
  3. 數據庫層面:MySQL `LEFT()` 按字節,`SUBSTRING()` 按字符 (需表字符集 utf8mb4),可直接 `SELECT SUBSTRING(title, 1, 20) FROM ...` 推算給 DB 做。
  4. 前端配合:CSS `text-overflow: ellipsis; white-space: nowrap; overflow: hidden;` 純前端截斷無需後端介入,性能最優。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved