在中文信息處理系統中,按拼音首字母排序、檢索是剛需。但網上流傳的多數方案基於 **GB2312 (6763 字)**,無法覆蓋 **GBK (21003 字)** 中的生僻字(如人名「鎕、鑫」、地名「垚、堃」),導致這類字符返回空或亂碼。
本文提供一套基於 **GBK 編碼表直接計算** 的完整解決方案,支持全字符集,無需第三方庫,單文件即用。
GBK 編碼兼容 GB2312,並擴展了兩個區域。每個漢字佔 2 字節,首字節為高位,次字節為低位。根據首字節範圍分為三塊,各有專屬映射表與計算公式:
| 區域 | 首字節範圍 | 次字節範圍 | 含字符數 | 映射表變量 | 地址計算公式 |
|---|---|---|---|---|---|
| GB2312 兼容區 | 0xB0 (176) ~ 0xF7 (247) | 0xA1 (161) ~ 0xFE (254) | 6763 | GB_2312 | (高位-176)*94 + (低位-161) |
| GBK/3 擴展區 | 0x81 (129) ~ 0xA0 (160) | 0x40 (64) ~ 0xFE (254) (除 0x7F) | 6080 | GBK_3 | (高位-129)*191 + (低位-64) |
| GBK/4 擴展區 | 0xAA (170) ~ 0xFE (254) | 0x40 (64) ~ 0xFE (254) (除 0x7F) | 8160 | GBK_4 | (高位-170)*191 + (低位-64) |
import java.io.UnsupportedEncodingException;
public class PinyinUtil {
// GB2312 區映射表 (6763 字符,每字符 1 字節存首字母)
private static final String GB_2312 = "aaaaaaaa...zzzzzz"; // 省略,完整表見文末
// GBK/3 區映射表
private static final String GBK_3 = "ksxsm sdqly...yjysylpnyyxdb"; // 省略
// GBK/4 區映射表
private static final String GBK_4 = "kxxmzjxstt...sypszsjczc"; // 省略
/**
* 獲取字符串拼音首字母 (支持 GBK 全字符集)
* @param hzString 輸入漢字字符串
* @return 小寫首字母字符串,非漢字保留原樣 (ASCII)
*/
public static String getFirstLetters(String hzString) throws UnsupportedEncodingException {
if (hzString == null || hzString.isEmpty()) return "";
StringBuilder result = new StringBuilder();
byte[] bytes = hzString.getBytes("GBK"); // 關鍵:強制 GBK 編碼
int i = 0, len = bytes.length;
while (i < len) {
int b1 = bytes[i++] & 0xFF; // 無符號化
// ASCII 字符直接保留
if (b1 < 128) {
result.append((char) b1);
continue;
}
if (i >= len) break; // 防止截斷
int b2 = bytes[i++] & 0xFF;
int index = -1;
char pyChar = '?';
if (b1 >= 0xB0 && b1 <= 0xF7 && b2 >= 0xA1) { // GB2312 區
index = (b1 - 0xB0) * 94 + (b2 - 0xA1);
if (index >= 0 && index < GB_2312.length()) pyChar = GB_2312.charAt(index);
}
else if (b1 >= 0x81 && b1 <= 0xA0) { // GBK/3 區
index = (b1 - 0x81) * 191 + (b2 - 0x40);
if (index >= 0 && index < GBK_3.length()) pyChar = GBK_3.charAt(index);
}
else if (b1 >= 0xAA && b1 <= 0xFE) { // GBK/4 區
index = (b1 - 0xAA) * 191 + (b2 - 0x40);
if (index >= 0 && index < GBK_4.length()) pyChar = GBK_4.charAt(index);
}
if (pyChar != '?') result.append(Character.toLowerCase(pyChar));
}
return result.toString();
}
// 測試
public static void main(String[] args) throws Exception {
String test = "光谷金融港镕軍國琪"; // 含生僻字:镕(róng)、琪(qí)
System.out.println(getFirstLetters(test)); // 輸出: ggjrgrjgq
}
}
int b1 = bytes[i++] & 0xFF; // Java byte 是有符號 -128~127,需轉為 0~255
否則負數字節會導致索引計算錯誤。
i >= len 防止奇數長度字節數組越界。index < table.length() 防止表數據不全導致 StringIndexOutOfBoundsException。ASCII (0~127) 直接按原字符輸出,保留數字、英文、標點,便於混合排序。
char[] 替代 String,避免 charAt 方法調用開銷。byte[] 到字符的映射 (LRU Cache),大幅提升重複調用速度。StringBuilder 和 byte[] 緩衝區。三個映射表字符串 (GB_2312, GBK_3, GBK_4) 由標準 GBK 編碼表按拼音區位序生成,每位對應一個漢字的拼音首字母。完整表極長 (總長 ~21KB),此處省略,完整代碼請參考項目源碼或自行按區位表生成。
| 優勢 | 局限 |
|---|---|
| 零依賴,單類文件即用 | 僅支持 GBK 編碼環境,UTF-8 需先轉碼 |
| 覆蓋 21003 字符,含生僻字 | 多音字僅取最常用讀音首字母 |
| 無詞庫依賴,部署極簡 | 不支持詞組級拼音 (如「長著」 vs 「張著」) |
| 性能極高,O(n) 線性掃描 | 映射表佔用 ~21KB 靜態內存 |
本方案通過直接解析 GBK 字節流、按區位計算索引、查表映射首字母,完美解決了生僻字拼音首字母獲取難題。代碼純 Java 實現,無第三方依賴,適合嵌入式、Android、服務端等各類項目直接集成。