中文排序、索引、檢索常需「拼音首字母」。本文提供三種實作:GB2312 區位碼查表法 (相容舊專案)、Unicode 範圍映射法 (支援擴展區)、Windows Globalization API (現代 .NET、跨平台、最準確)。
原理:GB2312 編碼中,一級漢字 (0xB0A1-0xD7FE) 按拼音排序,二級漢字 (0xD8A1-0xF7FE) 同理。透過區位碼對照表直接映射首字母。
using System;
using System.Text;
public static class PinyinHelper
{
// 一級漢字首字母對照表 (區碼 16-87 → 字母)
private static readonly string[] _firstLetterTable = new[]
{
"A","A","A","A","A","A","A","A","B","B","B","B","B","B","C","C","C","C","C","C","C","C","C",
"D","D","D","D","D","D","D","D","D","E","E","F","F","F","F","F","G","G","G","G","G","G","G",
"H","H","H","H","H","H","H","H","H","J","J","J","J","J","J","J","K","K","K","K","K","K","L",
"L","L","L","L","L","M","M","M","M","M","M","M","N","N","N","O","O","O","P","P","P","P","P",
"Q","Q","Q","R","R","R","R","R","S","S","S","S","S","S","S","S","T","T","T","T","T","W","W",
"W","X","X","X","X","X","Y","Y","Y","Y","Y","Y","Y","Y","Z","Z","Z","Z","Z","Z"
}; // 共 72 區 (16-87)
///
/// 取得字串每個字的拼音首字母 (大寫),非漢字原樣保留
///
public static string GetFirstLetters(string input)
{
if (string.IsNullOrEmpty(input)) return string.Empty;
var sb = new StringBuilder(input.Length);
var bytes = Encoding.GetEncoding("GB2312").GetBytes(input);
for (int i = 0; i < bytes.Length; )
{
byte b = bytes[i];
if (b < 0x80) // ASCII
{
sb.Append((char)b);
i++;
}
else if (i + 1 < bytes.Length) // 雙位元組
{
ushort zone = (ushort)(b - 0xA0); // 區碼 16-94
ushort pos = (ushort)(bytes[i+1] - 0xA0); // 位碼 1-94
i += 2;
if (zone >= 16 && zone <= 87) // 一級漢字區
{
sb.Append(_firstLetterTable[zone - 16]);
}
else if (zone >= 16+72 && zone <= 94) // 二級漢字區 (16+72=88 到 94)
{
// 二級漢字較複雜,這裡簡單回傳 'Z' 或可擴充完整對照表
sb.Append('Z');
}
else
{
sb.Append('?'); // 符號區
}
}
else { sb.Append('?'); i++; }
}
return sb.ToString();
}
}
缺點:僅支援 GB2312 (6763 字);二級漢字首字母需 3000+ 條對照表;不支援擴展區 (HKSCS、Big5、Unicode 擴展 A/B/C/D)。
原理:Unicode 將漢字按部首/筆畫排序,**非拼音順序**,故無法直接用碼位推算。需外部資料檔 (如 Unihan_Readings.txt) 建立映射字典。
// 概念程式碼:載入 Unihan 資料建立 Dictionary
public static class UnicodePinyin
{
private static readonly Dictionary _pinyinMap = LoadUnihan(); // 需預先載入
public static string GetFirstLetters(string input)
{
var sb = new StringBuilder();
foreach (char c in input)
{
if (_pinyinMap.TryGetValue(c, out var py))
sb.Append(py[0]); // 取第一個讀音首字母
else if (char.IsDigit(c) || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
sb.Append(char.ToUpper(c));
else
sb.Append(c);
}
return sb.ToString();
}
private static Dictionary LoadUnihan()
{
// 解析 Unicode.org 提供的 Unihan_Readings.txt
// 格式: U+4E00 kMandarin yī
// 需處理多音字、異體字、擴展區
return new Dictionary(); // 實際專案建議嵌入資源檔
}
}
資料量大 (~10萬字),建議編譯期嵌入二進位資源或使用 SQLite 查詢。
Windows.Globalization.PhoneticGenerator (UWP/WinUI) 或 System.Globalization 搭配 CultureInfo 的 TextInfo 並無直接拼音 API。**最佳跨平台方案**是使用 Microsoft.International.Converters.PinyinConverter (NuGet) 或 Pinyin.NET。
// NuGet: Microsoft.International.Converters.PinyinConverter
using Microsoft.International.Converters.PinyinConverter;
public static class PinyinOfficial
{
public static string GetFirstLetters(string input)
{
var sb = new StringBuilder();
foreach (char c in input)
{
if (ChineseChar.IsValidChar(c))
{
var cc = new ChineseChar(c);
// 取第一個拼音的首字母
var py = cc.Pinyins[0]; // 如 "yi1"
sb.Append(char.ToUpper(py[0]));
}
else
{
sb.Append(char.ToUpper(c));
}
}
return sb.ToString();
}
}
// NuGet: Pinyin.NET
using Pinyin.NET;
public static class PinyinNetHelper
{
public static string GetFirstLetters(string input, PinyinFormat format = PinyinFormat.FirstLetter)
{
return PinyinHelper.GetPinyin(input, format); // 內建 FirstLetter/WithTone/WithoutTone
}
}
ChineseLinguistics (實驗性)
// .NET 7+ System.Globalization.ChineseLinguistics (預覽功能)
using System.Globalization;
public static class DotNetChinese
{
public static string GetFirstLetters(string input)
{
// 目前 API 仍在演進,請參考官方文件
return string.Concat(input.Select(c =>
ChineseLinguistics.GetPinyin(c).FirstOrDefault()?[0].ToString().ToUpper() ?? c.ToString()));
}
}
| 方案 | 支援字集 | 多音字 | 跨平台 | 相依性 | 速度 (10萬字) |
|---|---|---|---|---|---|
| GB2312 查表 | 6763 | ❌ | ✅ | 無 | ~5ms |
| Unihan 字典 | ~10萬 | ✅ | ✅ | 資料檔 | ~20ms |
| PinyinConverter | ~2萬 | ✅ | ❌ (仅 FW) | NuGet | ~30ms |
| Pinyin.NET | ~10萬 | ✅ | ✅ | NuGet | ~15ms |
public class SearchSuggestionService
{
private readonly Dictionary> _index = new();
public void BuildIndex(IEnumerable keywords)
{
foreach (var kw in keywords)
{
string key = PinyinNetHelper.GetFirstLetters(kw).ToUpper();
if (!_index.ContainsKey(key)) _index[key] = new List();
_index[key].Add(kw);
}
}
public IEnumerable Suggest(string prefix)
{
string key = PinyinNetHelper.GetFirstLetters(prefix).ToUpper();
return _index.TryGetValue(key, out var list) ? list : Enumerable.Empty();
}
}
「取拼音首字母」看似簡單,實則涉及字集覆蓋、多音字、效能、跨平台等工程決策。**現代專案首選 Pinyin.NET**,舊專案可用 GB2312 查表法快速落地。若在資料庫層面解決排序/檢索,更能一勞永逸。