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

PHP 中英混排字串處理利器:判斷中文字節、反轉、截取、分行完整函數庫

     編輯:PHP 綜合知識

痛點:PHP 內建字串函數「不懂中文」

`strlen()` 算位元組、`strrev()` 直接逐位元組反轉、`substr()` 硬切位元組——對於 UTF-8/GBK 雙位元組中文,這些函數都會產生**亂碼、斷字、長度誤判**。雖然現代 PHP 建議用 `mbstring` 或 `intl` 擴展,但在舊專案、受限環境、或需要極致效能的場景下,一套純 PHP、無依賴、處理 **GBK/Big5 雙位元組編碼** 的工具函數庫仍有實戰價值。本文整理並優化一組經典函數:`is_chinese`、`cstrrev`、`csubstr`、`cleft`、`cright`、`ctext_wrap`。

核心判斷函數:is_chinese()

/**
 * 判斷字串指定位置是否為中文字元的左/右半邊
 * @param string $str 目標字串 (GBK/Big5 編碼)
 * @param int $location 位元組索引 (0-based)
 * @return int -1=左半部 | 0=非中文(ASCII) | 1=右半部
 */
function is_chinese(&$str, $location) {
    $ch = true;
    $i = $location;
    // 向前掃描:連續高位元組 (>0xA0) 視為中文雙位元組序列
    while (isset($str[$i]) && ord($str[$i]) > 0xA0 && $i >= 0) {
        $ch = !$ch; // 翻轉標記
        $i--;
    }
    if ($i != $location) { // 掃描過程中遇到高位元組
        return $ch ? 1 : -1; // 最後狀態決定左右
    }
    return 0; // 全程無高位元組 = ASCII
}

原理:GBK/Big5 中文字首位元組 `> 0xA0`,次位元組也 `> 0xA0`。從目標位置向前掃描,遇到幾個高位元組就翻轉幾次旗標,最終旗標即揭示目標位置屬於雙位元組的第幾個字節。

中文安全反轉:cstrrev()

function cstrrev(&$str) {
    $len = strlen($str);
    $rev = '';
    $chinese = false;
    for ($i = $len - 1; $i >= 0; $i--) {
        if (ord($str[$i]) > 0xA0) {
            $chinese = !$chinese;
            if (!$chinese) { // 遇到左半部,輸出「右+左」
                $rev .= $str[$i] . $str[$i+1];
            }
        } else {
            $rev .= $str[$i]; // ASCII 直接輸出
        }
    }
    return $rev;
}
// 測試:cstrrev('abc中文') → '文中cba'

中文安全截取:csubstr() (核心瑞士刀)

/**
 * @param string $str 來源字串
 * @param int $start 起始位置 (字元數或位元組數,視 $cn_len)
 * @param int $length 截取長度 (0=到尾)
 * @param bool $ltor true=左→右 | false=右→左
 * @param int $cn_len 2=按位元組計 | 1=按字元計(中文算1)
 * @return string 截取結果
 */
function csubstr(&$str, $start=0, $length=0, $ltor=true, $cn_len=2) {
    if ($length === 0) $length = strlen($str);
    if (!$ltor) $str = cstrrev($str); // 反轉處理右→左

    if ($cn_len == 1) { // 按字元數計算 (中文算1)
        // 將 $start/$length 換算為位元組偏移
        for ($i=0, $fs=0; $i < $start; $fs++) $i += (ord($str[$fs]) <= 0xA0) ? 1 : 0.5;
        for ($i=0, $fe=$fs; $i < $length; $fe++) $i += (ord($str[$fe]) <= 0xA0) ? 1 : 0.5;
        $length = $fe - $fs;
    } else { // 按位元組計算,但避開切中中文
        $fs = (is_chinese($str, $start) == 1) ? $start - 1 : $start; // 落在右半部則左移
        $fe = $start + $length - 1;
        $end = (is_chinese($str, $fe) == -1) ? $fe - 1 : $fe; // 落在左半部則左移
        $length = $end - $fs + 1;
    }
    $result = substr($str, $fs, $length);
    if (!$ltor) $result = cstrrev($result);
    return $result;
}

便利包裝函數

// 取左 N 字元/位元組
function cleft(&$str, $length, $cn_len=2) { return csubstr($str, 0, $length, true, $cn_len); }

// 取右 N 字元/位元組
function cright(&$str, $length, $cn_len=2) {
    $rev = cstrrev($str);
    $cut = csubstr($rev, 0, $length, true, $cn_len);
    return cstrrev($cut);
}

// 文章自動分行 (依字元寬度換行,避免斷字)
function ctext_wrap(&$text, $width=60, $br="
") { $lines = explode("\n", $text); // 原文以 \n 分段 $out = ''; foreach ($lines as $line) { $len = strlen($line); for ($j=0; $j < $len; $j += $width) { $p = $j + $width - 1; $k = 0; // 向前尋找安全斷點:非中文右半部、非空格 while ($p < $len && !is_chinese($line, $p) && $line[$p] != ' ' && $p > $j) { $k++; $p--; } if ($p == $j) $k = 0; // 整行無安全點,硬切 $out .= csubstr($line, $j, $width - $k) . $br; $j -= $k; // 下一輪從安全點後開始 } } return $out; }

使用範例與測試

<?php
header('Content-Type: text/html; charset=gbk'); // 重要:頁面編碼需與函數假設一致
$s = "Hello世界123中文";

echo "原文: $s
"; echo "長度(bytes): ".strlen($s)."
"; echo "反轉: ".cstrrev($s)."
"; echo "左6字元: ".cleft($s, 6, 1)."
"; // Hello世 echo "右5字元: ".cright($s, 5, 1)."
"; // 123中文 echo "截取(2,6): ".csubstr($s, 2, 6, true, 1)."
"; // llo世 // 文章分行測試 $article = "這是一段很長的中英文混合文字Hello World用來測試自動分行功能是否會正確避開中文斷字問題。"; echo ctext_wrap($article, 20); ?>

⚠️ 關鍵限制與現代替代方案

限制說明現代最佳實踐
編碼綁定假設 **GBK/Big5** (雙位元組,首字節 >0xA0);**UTF-8 中文首字節 ≥ 0xE0**,邏輯完全不同專案全面改用 **UTF-8** + `mbstring` 函數 (`mb_strlen`, `mb_substr`, `mb_strrev` 等)
無視組合字元不處理 Emoji、注音符號、ZWJ 序列等多碼位字元用 `grapheme_*` 函數 (需 `intl` 擴展) 或 `symfony/string` 套件
效能逐位元組掃描,長字串 O(n²) 風險`mbstring` 底層 C 實現,快 10-100 倍
維護性手寫邊界邏輯極易出錯引入成熟庫:`voku/portable-utf8` (無擴展依賴)、`symfony/string`

遷移建議:從此函數庫平滑升級到 mbstring

// 直接對應替換表
strlen($s)        → mb_strlen($s, 'UTF-8')
substr($s, $a, $b)→ mb_substr($s, $a, $b, 'UTF-8')
strrev($s)        → mb_strrev($s) // 需自行實現或用 voku 库
// 正則加上 u 修飾符
preg_match('/pattern/u', $s)

若受限無法安裝擴展,可引入 **`voku/portable-utf8`** (Composer: `composer require voku/portable-utf8`),它純 PHP 實現 `UTF8::strlen()`、`UTF8::substr()` 等完整 API,且自動偵測 `mbstring` 可用時優先使用原生函數,效能與相容性雙贏。

這組函數庫見證了 PHP 早期開發者在無 `mbstring` 環境下的智慧與妥協。今天,它更適合作為**理解字元編碼底層邏輯的教學素材**,而非生產環境首選。理解它,才能更珍惜 `mbstring` 與 `intl` 帶來的便利。

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