在文章列表、摘要顯示等情境中,常需截取指定長度的字串並附加「...」。挑戰在於:中文字元佔 2 個位元組(UTF-8 下為 3 位元組),英文佔 1 個位元組。若單純用 Substring 按字元數截取,會導致顯示寬度不一;若按位元組截取,可能將中文字元切成兩半產生亂碼。
利用正則將中文字元替換為雙字元(如 "zz"),再量測字串長度,即可得到「視覺位元組長度」。
using System.Text.RegularExpressions;
public static class StringHelper
{
///
/// 截取中英混合字串,依視覺位元組長度(中文算 2,英文算 1)截取,並附加省略號。
///
/// 原始字串
/// 目標位元組長度(不含省略號)
/// 截取後字串,若未超長則回傳原字串
public static string TruncateByVisualLength(string input, int byteLength)
{
if (string.IsNullOrEmpty(input)) return input;
// 計算視覺長度:中文->zz (長度2),其他保持
string visual = Regex.Replace(input, "[\u4e00-\u9fa5]", "zz");
if (visual.Length <= byteLength)
return input; // 未超長
// 從尾端往前嘗試截取
for (int i = input.Length; i >= 0; i--)
{
string candidate = input.Substring(0, i);
string candVisual = Regex.Replace(candidate, "[\u4e00-\u9fa5]", "zz");
if (candVisual.Length <= byteLength - 3) // 預留「...」3個位元組
{
return candidate + "...";
}
}
return "..."; // 極端情況
}
}
string s1 = "中國人要啊abc呀~";
string s2 = "1中國人23456abc呀~";
Console.WriteLine(StringHelper.TruncateByVisualLength(s1, 8)); // 中國人要...
Console.WriteLine(StringHelper.TruncateByVisualLength(s2, 8)); // 1中國人2...
上述正則 [\u4e00-\u9fa5] 僅涵蓋基本中日韓統一表意文字。若需支援擴展區(如 𠮷 U+20BB7)或 Emoji,建議改用 StringInfo 或 Rune(.NET Core 3.0+)逐字元計算:
public static string TruncateByVisualLengthAdvanced(string input, int byteLength)
{
if (string.IsNullOrEmpty(input)) return input;
int visualLen = 0;
int charIndex = 0;
foreach (Rune rune in input.EnumerateRunes())
{
// 簡易判斷:東亞寬字元視為 2,其餘為 1
int w = (rune.Value >= 0x4E00 && rune.Value <= 0x9FFF) || // CJK 統一表意文字
(rune.Value >= 0x3400 && rune.Value <= 0x4DBF) || // CJK 擴展 A
(rune.Value >= 0x20000 && rune.Value <= 0x2FFFF) // CJK 擴展 B~F
? 2 : 1;
if (visualLen + w > byteLength - 3) break;
visualLen += w;
charIndex += rune.Utf16SequenceLength;
}
if (charIndex >= input.Length) return input;
return input.Substring(0, charIndex) + "...";
}
中英混合字串截取的核心在於視覺寬度計算。Regex 替換法簡單易懂,適合傳統 .NET Framework 專案;新專案建議使用 Rune 逐字元處理,支援完整 Unicode 並效能更佳。實務上應根據專案框架版本與字元集需求選擇適當實作。