中文首字母拼音轉換是通訊錄排序、城市選擇器、搜尋索引、URL 友善化等功能的基礎需求。PHP 核心不內建此功能,開發者需自行實作或引入第三方套件。本文深入解析GB2312 區位碼對照表法原理與缺陷,提供多音字處理策略,並推薦現代Composer 套件與效能優化方案。
GB2312 (及其超集 GBK/GB18030) 將漢字編碼為雙位元組,範圍 0xB0A1 - 0xF7FE。拼音排序大致遵循區位碼順序,因此可建立區位碼區間 → 首字母對照表。
<?php
/**
* 根據 GB2312/GBK 編碼獲取單一漢字首字母 (大寫)
* @param string $char 單一漢字 (需為 GB2312/GBK 編碼)
* @return string 首字母 A-Z,非漢字返回原字元,* 表未知
*/
function getFirstLetterGB(string $char): string {
// 確保輸入為 GB2312 編碼
if (mb_detect_encoding($char, 'UTF-8', true)) {
$char = iconv('UTF-8', 'GB2312//IGNORE', $char);
}
// 非雙位元組直接返回 (ASCII、數字、符號)
if (strlen($char) !== 2) {
return strtoupper($char);
}
// 計算區位碼 (GB2312: 高位 0xB0-0xF7, 低位 0xA1-0xFE)
$high = ord($char[0]);
$low = ord($char[1]);
$zone = $high - 0xA0; // 1-94
$pos = $low - 0xA0; // 1-94
$gbCode = $zone * 100 + $pos; // 區位碼 101-9494
// 區位碼轉拼音首字母對照表 (依 GB2312 一級漢字排序)
// 來源: 標準 GB2312 拼音排序區間
static $pinyinRanges = [
['A', 1601, 1636], // 啊-安
['B', 1637, 1832], // 八-彙
['C', 1833, 2077], // 才-錯
['D', 2078, 2272], // 搭-動
['E', 2273, 2301], // 俄-餓
['F', 2302, 2432], // 發-服
['G', 2433, 2593], // 改-滾
['H', 2594, 2786], // 孩-貨
['J', 2787, 3105], // 機-鏡
['K', 3106, 3211], // 開-礦
['L', 3212, 3471], // 來-淚
['M', 3472, 3634], // 媽-妹
['N', 3635, 3721], // 那-奴
['O', 3722, 3729], // 歐-區
['P', 3730, 3857], // 爬-仆
['Q', 3858, 4026], // 期-全
['R', 4027, 4036], // 然-熔
['S', 4037, 4217], // 掃-絲
['T', 4218, 4357], // 塌-酗
['W', 4358, 4441], // 瓦-烏
['X', 4442, 4606], // 希-敘
['Y', 4607, 4728], // 衣-軸
['Z', 4729, 4879], // 咱-總
];
foreach ($pinyinRanges as [$letter, $start, $end]) {
if ($gbCode >= $start && $gbCode <= $end) {
return $letter;
}
}
return '*'; // 罕用字、擴展區字元
}
/**
* 獲取字串首字母縮寫 (每個漢字取首字母)
* @param string $str UTF-8 字串
* @return string 如 "北京市" → "BJS"
*/
function getPinyinAbbr(string $str): string {
$gbStr = iconv('UTF-8', 'GB2312//IGNORE', $str);
$len = strlen($gbStr);
$result = '';
for ($i = 0; $i < $len; $i++) {
$ord = ord($gbStr[$i]);
if ($ord >= 0xB0) { // 漢字雙位元組
if ($i + 1 < $len) {
$char = $gbStr[$i] . $gbStr[$i + 1];
$result .= getFirstLetterGB($char);
$i++; // 跳過低位元組
}
} else { // ASCII
$result .= strtoupper($gbStr[$i]);
}
}
return $result;
}
// 測試
echo getPinyinAbbr('北京市'); // BJS
echo getPinyinAbbr('長沙'); // CS
echo getPinyinAbbr('重慶'); // ZQ (多音字 '重' 只能取最常見 zhòng→Z)
?>
| 問題 | 影響 | 例子 |
|---|---|---|
| 多音字單一映射 | 「重」只映射 Z (zhòng),無法處理 chóng (C) | 重慶, 重複, 重量 |
| 依賴 GB2312 編碼 | UTF-8 環境需轉碼,效能損耗;不支援 GB2312 外字元 (如 😀、生僻字) | Emoji、港台慣用字 |
| 無法處理姓名/地址慣用讀音 | 「單」姓讀 Shàn 非 Dān,「曾」姓讀 Zēng 非 Céng | 單田芳、曾國藩 |
| 效能瓶頸 | 逐字轉碼 + 線性查找區間,長字串效能差 | 批次處理萬筆資料 |
composer require overtrue/pinyin
<?php
use Overtrue\Pinyin\Pinyin;
$pinyin = new Pinyin();
// 基礎用法
echo $pinyin->permalink('帶著希望去旅行'); // dai-zhe-xi-wang-qu-lv-xing
echo $pinyin->abbr('北京市'); // BJS
echo $pinyin->sentence('重慶市'); // ['chong', 'qing', 'shi']
// 多音字處理
echo $pinyin->permalink('重複'); // chong-fu (預設常用讀音)
echo $pinyin->permalink('重複', '', true); // zhong-fu (啟用多音字模式,返回陣列)
// 姓名模式 (姓氏優先特殊讀音)
echo $pinyin->name('單田芳'); // ['shan', 'tian', 'fang']
echo $pinyin->name('曾國藩'); // ['zeng', 'guo', 'fan']
// 自定義詞典
$pinyin->addDictionary([
'我的專有名詞' => 'wo-de-zhuan-you-ming-ci',
]);
?>
composer require janez/pinyin
<?php
use Janez\Pinyin\Pinyin;
$pinyin = new Pinyin();
echo $pinyin->getFirstLetters('北京'); // BJ
echo $pinyin->getPinyin('北京'); // bei3 jing1 (帶聲調數字)
echo $pinyin->getPinyin('北京', Pinyin::TONE_MARKS); // běi jīng (Unicode 聲調符號)
?>
composer require symfony/string
<?php
use Symfony\Component\String\Slugger\AsciiSlugger;
$slugger = new AsciiSlugger();
echo $slugger->slug('中文標題')->lower(); // zhong-wen-biao-ti
// 內建支援中文、日文、韓文、越南文等
?>
<?php
function getPinyinCached(string $str, Pinyin $pinyin): string {
$key = 'pinyin:' . md5($str);
$cached = apcu_fetch($key);
if ($cached !== false) return $cached;
$result = $pinyin->abbr($str);
apcu_store($key, $result, 86400); // 快取 1 天
return $result;
}
-- MySQL 8.0+ 虛擬欄位 (不佔儲存空間,查詢時即時計算)
ALTER TABLE `cities`
ADD COLUMN `pinyin_abbr` VARCHAR(50)
GENERATED ALWAYS AS (UPPER(SUBSTRING(`name`, 1, 1))) VIRTUAL; -- 簡易版,實際需自定義函數
-- 或使用 Trigger 寫入實體欄位 (支援索引)
CREATE TRIGGER `trg_city_pinyin` BEFORE INSERT ON `cities`
FOR EACH ROW BEGIN
SET NEW.pinyin_abbr = get_pinyin_abbr(NEW.name); -- 需建立 UDF 或存儲函數
END;
<?php
// 錯誤:迴圈中逐筆轉碼 + 查詢 (極慢)
foreach ($cities as $city) {
$city->pinyin = getPinyinAbbr($city->name);
}
// 正確:一次性取得所有名稱,批次轉換
$names = array_column($cities, 'name');
$pinyinMap = [];
foreach ($names as $name) {
$pinyinMap[$name] = $pinyin->abbr($name); // 利用快取
}
foreach ($cities as &$city) {
$city->pinyin = $pinyinMap[$city->name];
}
-- 建立首字母索引,支援 "WHERE pinyin_abbr LIKE 'BJ%'"
ALTER TABLE `users` ADD INDEX `idx_pinyin_abbr` (`pinyin_abbr`);
-- 查詢:輸入 "bj" 找出所有北京相關
SELECT * FROM `users` WHERE `pinyin_abbr` LIKE 'BJ%';
-- 通訊錄分組:SELECT SUBSTRING(pinyin_abbr,1,1) AS initial, COUNT(*) FROM users GROUP BY initial;
中文首字母轉換看似簡單,實則涉及編碼轉換、多音字消歧、姓名慣用讀音、生僻字支援、效能優化等複雜議題。強烈建議棄用手寫 GB2312 對照表,改用 overtrue/pinyin 等成熟套件,內建完整詞庫、多音字策略、姓名模式、分詞支援,並持續維護更新。配合 APCu/Redis 快取、資料庫 Generated Column/Trigger 預計算、批次處理最佳化,即可滿足千萬級資料的高效能拼音索引需求。對於特殊業務 (如醫學術語、方言地名),可擴充自定義詞典覆寫預設讀音。