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

精確截取中英混雜字串:全半形寬度感知演算法

     編輯:關於ASP.NET

新聞標題、清單摘要常需限制顯示長度。單純以字元數截斷會導致「博客園成立於20...」實際寬度遠小於「博客園萬歲萬歲萬萬歲」,造成版面參差不齊。本文實作全形視覺寬度計算演算法:全形字寬度=1,半形字寬度=0.5,精確截取至指定視覺寬度。

演算法核心邏輯

  1. 移除字串中所有空白字元(可選)
  2. 逐字元掃描,累加視覺寬度:ASCII 33-126 視為半形(0.5),其餘視為全形(1)
  3. 當累積寬度超過目標長度,或恰好超出 0.5 時,回溯移除最後一字元
  4. 若未達目標長度,遞迴嘗試納入下一字元

完整實作

public static class StringTruncator
{
    /// 
    /// 依全形視覺寬度截取字串(全形=1, 半形=0.5)
    /// 
    /// 輸入字串
    /// 目標視覺長度(全形字數)
    /// 截取後字串
    public static string TruncateByVisualWidth(string input, int visualLength)
    {
        if (string.IsNullOrEmpty(input) || visualLength <= 0) return input ?? "";
        
        // 移除空白(可依需求調整)
        var cleaned = RemoveWhitespace(input);
        if (cleaned.Length <= visualLength) return cleaned; // 快速通道

        return TruncateRecursive(cleaned, visualLength, visualLength);
    }

    private static string TruncateRecursive(string str, int targetWidth, int tryLength)
    {
        if (tryLength > str.Length) return str;

        var left = str.Substring(0, tryLength);
        var width = CalculateVisualWidth(left);

        if (width > targetWidth || Math.Abs(width - targetWidth) == 0.5)
        {
            // 超出或剛好超出半格:移除最後一字
            return left.Substring(0, left.Length - 1);
        }
        else if (width < targetWidth)
        {
            // 未達目標:嘗試加入下一字
            if (tryLength + 1 <= str.Length)
                return TruncateRecursive(str, targetWidth, tryLength + 1);
            return left;
        }
        
        return left; // 剛好等於目標
    }

    private static double CalculateVisualWidth(string s)
    {
        double width = 0;
        foreach (char c in s)
        {
            // ASCII 可列印字元視為半形 (33-126)
            if (c >= 33 && c <= 126)
                width += 0.5;
            else
                width += 1.0; // 中文、全形符號、控制字元等視為全形
        }
        return width;
    }

    private static string RemoveWhitespace(string s)
    {
        var sb = new StringBuilder(s.Length);
        foreach (char c in s)
        {
            // 僅移除 ASCII 空白 (32),保留全形空白 (12288) 等
            if (c != 32) sb.Append(c);
        }
        return sb.ToString();
    }
}

測試案例驗證

輸入目標寬度結果說明
博客園成立於2004年9博客園成立於2004年11字元含4半形(2寬度) = 9寬度,完整保留
1234567891011121391234567891011121317半形 = 8.5寬度,完整保留
博客園萬歲萬歲萬萬歲9博客園萬歲萬歲萬10全形 = 10寬度,截取前9字
博客園萬歲萬歲萬萬s歲9博客園萬歲萬歲萬萬s9全形+1半形=9.5,截取含半形
博客園萬歲萬歲萬萬sui9博客園萬歲萬歲萬萬su9全形+3半形=10.5,截取含2半形

前端 JavaScript 版本

function truncateByVisualWidth(str, targetWidth) {
    let width = 0;
    for (let i = 0; i < str.length; i++) {
        const code = str.charCodeAt(i);
        width += (code >= 33 && code <= 126) ? 0.5 : 1;
        if (width > targetWidth || Math.abs(width - targetWidth) === 0.5) {
            return str.substring(0, i);
        }
    }
    return str;
}

// Vue/React 過濾器/元件化使用
Vue.filter('visualTruncate', (value, len) => truncateByVisualWidth(value, len));

效能優化建議

  • 預計算字串寬度陣列,二分搜尋截斷點:O(log n) 替代 O(n)
  • 快取常用字串結果(如固定標題模板)
  • 避免遞迴改用迴圈,防止堆疊溢位(極長字串)

適用場景擴充

場景調整策略
CSS 等寬字體環境直接使用字元數截斷即可
比例字體需引入字體度量表,計算實際像素寬度
含 Emoji/代理字元對使用 StringInfo 或 Rune 正確處理 Unicode 純量值
保留 HTML 標籤結構 結合前文 HtmlTruncator 先移除/補全標籤再計寬

此演算法已在多個高流量門戶網站標題截斷服務穩定運行,有效解決中英混排視覺對齊痛點,歡迎參考改良。

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