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

PHP 微博字數統計算法:UTF-8/GBK 中英混排字元寬度計算

     編輯:PHP 綜合知識

中英混排字元寬度計算:微博式字數統計演算法

微博字數規則:中文/全形算 2 字元,英文/半形算 1 字元。標準 `strlen` 計算位元組數 (UTF-8 中文 3 位元組),`mb_strlen` 計算字元數 (中英皆算 1),均不符合需求。本文提供支援 UTF-8/GBK 的精確寬度計算演算法。

字元寬度規則定義

類別範例寬度Unicode 範圍
ASCII 列印字元a-z A-Z 0-9 !@#...1U+0020 - U+007E
控制字元Tab, LF, CR1U+0009, U+000A, U+000D
全形 ASCIIABC 123 !@#2U+FF01 - U+FF5E
中日韓統一表意文字中 文 測 試2U+4E00 - U+9FFF 等
其他 CJK 符號。 , 、 ; : ?2U+3000 - U+303F
Emoji / 輔助平面😀 🎉 👍2U+1F000+

完整實作:strlen_weibo()

<?php
/**
 * 微博式字元寬度計算 (支援 UTF-8)
 * 
 * @param string $string  輸入字串
 * @param string $charset 編碼 (utf-8, gbk, gb2312, big5)
 * @return int 字元寬度總和
 */
function strlen_weibo($string, $charset = 'utf-8') {
    $n = $count = 0;
    $length = strlen($string);
    $charset = strtolower($charset);
    
    if ($charset === 'utf-8') {
        while ($n < $length) {
            $byte = ord($string[$n]);
            
            // ASCII 列印字元 + 控制字元 (寬度 1)
            if ($byte === 9 || $byte === 10 || (32 <= $byte && $byte <= 126)) {
                $n++;
                $count++;
            }
            // 2 位元組 UTF-8 序列 (U+0080 - U+07FF) - 稀有字元,寬度 2
            elseif (194 <= $byte && $byte <= 223) {
                $n += 2;
                $count += 2;
            }
            // 3 位元組 UTF-8 序列 (U+0800 - U+FFFF) - 中文主平面,寬度 2
            elseif (224 <= $byte && $byte <= 239) {
                $n += 3;
                $count += 2;
            }
            // 4 位元組 UTF-8 序列 (U+10000 - U+10FFFF) - Emoji/輔助平面,寬度 2
            elseif (240 <= $byte && $byte <= 247) {
                $n += 4;
                $count += 2;
            }
            // 5 位元組 (理論上 UTF-8 不超過 4 位元組,保留相容)
            elseif (248 <= $byte && $byte <= 251) {
                $n += 5;
                $count += 2;
            }
            // 6 位元組 (同上)
            elseif ($byte === 252 || $byte === 253) {
                $n += 6;
                $count += 2;
            }
            // 非法位元組序列,按 1 處理
            else {
                $n++;
                $count++;
            }
            
            // 防禦性檢查
            if ($count >= $length) break;
        }
        return $count;
    }
    
    // GBK/GB2312/Big5: 位元組值 > 127 視為雙位元組字元 (寬度 2)
    else {
        for ($i = 0; $i < $length; $i++) {
            if (ord($string[$i]) > 127) {
                $i++;  // 跳過第二位元組
                $count++;  // 雙位元組算寬度 2,這裡先 +1,下面再 +1
            }
            $count++;
        }
        return $count;
    }
}

// 測試
$test = "s@@你好";  // s(1) + 全形@(2) + 半形@(1) + 你(2) + 好(2) = 8
echo "字串: {$test}\n";
echo "寬度: " . strlen_weibo($test, 'utf-8') . "\n";  // 輸出: 8
?>

UTF-8 位元組序列識別邏輯圖解

位元組 1 範圍        位元組數  Unicode 範圍          典型字元          微博寬度
0xxxxxxx (0-127)       1       U+0000-U+007F       ASCII              1
110xxxxx (192-223)     2       U+0080-U+07FF       希臘/西里爾/阿拉伯   2
1110xxxx (224-239)     3       U+0800-U+FFFF       中日韓主平面         2
11110xxx (240-247)     4       U+10000-U+10FFFF    Emoji/古文字         2
111110xx (248-251)     5       (非標準 UTF-8)      -                  2
1111110x (252-253)     6       (非標準 UTF-8)      -                  2

現代替代方案:使用 PHP 8.1+ IntlChar / Grapheme

<?php
// 方案 1: grapheme_strlen (計算使用者感知字元數,非寬度)
$str = "s@@你好👍";
echo "Grapheme: " . grapheme_strlen($str) . "\n";  // 6 (s, @, @, 你, 好, 👍)

// 方案 2: IntlChar::charWidth (需 intl 擴充,PHP 8.1+)
function strlen_weibo_modern($str) {
    $width = 0;
    $graphemes = grapheme_str_split($str);  // PHP 8.0+
    foreach ($graphemes as $char) {
        $cp = IntlChar::ord($char);
        // 東亞寬度判斷 (近似)
        if (IntlChar::charType($cp) === IntlChar::CHAR_CATEGORY_OTHER_SYMBOL ||
            ($cp >= 0x1100 && $cp <= 0x115F) ||  // Hangul Jamo
            ($cp >= 0x2E80 && $cp <= 0xA4CF) ||  // CJK 範圍
            ($cp >= 0xAC00 && $cp <= 0xD7A3) ||  // Hangul Syllables
            ($cp >= 0xF900 && $cp <= 0xFAFF) ||  // CJK Compatibility
            ($cp >= 0xFE10 && $cp <= 0xFE1F) ||  // Vertical forms
            ($cp >= 0xFE30 && $cp <= 0xFE6F) ||  // CJK Compatibility Forms
            ($cp >= 0xFF00 && $cp <= 0xFF60) ||  // Fullwidth Forms
            ($cp >= 0x1F000 && $cp <= 0x1FFFF)   // Emoji 等
        ) {
            $width += 2;
        } else {
            $width += 1;
        }
    }
    return $width;
}

// 方案 3: mbstring 寬度模式 (PHP 7.2+ mb_strwidth)
echo "mb_strwidth: " . mb_strwidth($str, 'UTF-8') . "\n";  // 直接得到寬度!
// 注意: mb_strwidth 規則與微博略有差異,需實測驗證
?>

效能比較

<?php
function benchmark($fn, $str, $iterations = 10000) {
    $start = microtime(true);
    for ($i = 0; $i < $iterations; $i++) $fn($str);
    return microtime(true) - $start;
}

$testStr = str_repeat("中文English全形@半形@", 100);

echo "strlen_weibo (自訂): " . benchmark('strlen_weibo', $testStr) . "s\n";
echo "mb_strwidth (內建):  " . benchmark('mb_strwidth', $testStr) . "s\n";

/* 典型結果:
strlen_weibo: 0.45s  (PHP 層迴圈)
mb_strwidth:  0.08s  (C 層實作,快 5-6 倍)
*/
?>

應用場景與整合建議

  • 表單即時字數顯示:前端 JS 同步實作 (使用 String.prototype.codePointAt),後端驗證用 PHP
  • 內容截斷:配合寬度感知截取函數,避免半個中文字元
  • 資料庫欄位設計:VARCHAR 長度按最大寬度預估 (如 140 字元 → VARCHAR(280) for UTF-8)
  • API 回應:回傳 `width` 與 `char_count` 雙欄位,前端靈活選用

前端 JS 對應實作

<script>
function weiboLength(str) {
    let width = 0;
    for (let i = 0; i < str.length; i++) {
        const cp = str.codePointAt(i);
        // 簡易判斷:BMP 範圍內非 ASCII 視為寬度 2
        // 完整版需對照 Unicode 東亞寬度表
        if (cp > 0x7F && cp <= 0xFFFF) width += 2;
        else if (cp > 0xFFFF) width += 2;  // 輔助平面 (Emoji)
        else width += 1;
        // 處理代理對
        if (cp > 0xFFFF) i++;
    }
    return width;
}

console.log(weiboLength("s@@你好👍"));  // 10
</script>

掌握字元寬度計算核心邏輯,配合現代 `mb_strwidth` 或 `IntlChar`,能精確實作微博式字數限制,提升使用者體驗。

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