在中文系統開發中,常需將漢字轉換為拼音首字母,應用場景包含:通訊錄排序、搜尋索引、URL 友善化、資料分組等。本文深度解析兩種主流實作方案:GB2312 區間對照表法與二分法查找優化版,並提供完整可用程式碼。
ASC = (高位元組 * 256 + 低位元組) - 65536<?php
/**
* 取得單一漢字的拼音首字母(大寫)
* @param string $s0 單一漢字(UTF-8 編碼)
* @return string|null 首字母或 null
*/
function getFirstChar($s0) {
$fchar = ord($s0{0});
// 英數字直接返回大寫
if ($fchar >= ord("A") && $fchar <= ord("z")) return strtoupper($s0{0});
// UTF-8 轉 GB2312
$s1 = iconv("UTF-8", "gb2312", $s0);
$s2 = iconv("gb2312", "UTF-8", $s1);
$s = ($s2 == $s0) ? $s1 : $s0; // 確認轉換可逆
// 計算 GB2312 區位碼
$asc = ord($s{0}) * 256 + ord($s{1}) - 65536;
// 區間判斷(依 GB2312 拼音排序)
if ($asc >= -20319 && $asc <= -20284) return "A";
if ($asc >= -20283 && $asc <= -19776) return "B";
if ($asc >= -19775 && $asc <= -19219) return "C";
if ($asc >= -19218 && $asc <= -18711) return "D";
if ($asc >= -18710 && $asc <= -18527) return "E";
if ($asc >= -18526 && $asc <= -18240) return "F";
if ($asc >= -18239 && $asc <= -17923) return "G";
if ($asc >= -17922 && $asc <= -17418) return "H"; // 註:標準無 I
if ($asc >= -17417 && $asc <= -16475) return "J";
if ($asc >= -16474 && $asc <= -16213) return "K";
if ($asc >= -16212 && $asc <= -15641) return "L";
if ($asc >= -15640 && $asc <= -15166) return "M";
if ($asc >= -15165 && $asc <= -14923) return "N";
if ($asc >= -14922 && $asc <= -14915) return "O";
if ($asc >= -14914 && $asc <= -14631) return "P";
if ($asc >= -14630 && $asc <= -14150) return "Q";
if ($asc >= -14149 && $asc <= -14091) return "R";
if ($asc >= -14090 && $asc <= -13319) return "S";
if ($asc >= -13318 && $asc <= -12839) return "T";
if ($asc >= -12838 && $asc <= -12557) return "W";
if ($asc >= -12556 && $asc <= -11848) return "X";
if ($asc >= -11847 && $asc <= -11056) return "Y";
if ($asc >= -11055 && $asc <= -10247) return "Z";
return null;
}
/**
* 字串轉拼音首字母(支援中英混合)
* @param string $zh UTF-8 字串
* @return string 首字母串
*/
function pinyinFirst($zh) {
$ret = "";
$s1 = iconv("UTF-8", "gb2312", $zh);
$s2 = iconv("gb2312", "UTF-8", $s1);
$zh = ($s2 == $zh) ? $s1 : $zh; // 確保為 GB2312
for ($i = 0; $i < strlen($zh); $i++) {
$s1 = substr($zh, $i, 1);
$p = ord($s1);
if ($p > 160) { // 高位元組 > 160 為漢字區
$s2 = substr($zh, $i, 2); // 取雙位元組
$ret .= getFirstChar($s2);
$i++; // 跳過低位元組
} else {
$ret .= $s1; // 英數字保留
}
}
return strtoupper($ret);
}
// 測試
header("Content-Type: text/html; charset=UTF-8");
echo "測試字串:這是中文字符串<br/>";
echo "結果:" . pinyinFirst('這是中文字符串'); // 輸出:ZSZWZFC
?>
| 優點 | 缺點 |
|---|---|
| 程式碼簡短,無需外部資料表 | 依賴 GB2312 編碼順序,不支援繁體/擴展字 |
| 效能極佳,O(n) 線性掃描 | 需 iconv 擴展支援 |
| 邏輯直觀,易於維護修改 | 區間表需精確,易漏邊界字 |
<?php
class ChinesePinyin {
// 關鍵編碼節點:GB2312 編碼值 => 首字母
private $_pinyins = array(
176161 => 'A', 176197 => 'B', 178193 => 'C', 180238 => 'D',
182234 => 'E', 183162 => 'F', 184193 => 'G', 185254 => 'H',
187247 => 'J', 191166 => 'K', 192172 => 'L', 194232 => 'M',
196195 => 'N', 197182 => 'O', 197190 => 'P', 198218 => 'Q',
200187 => 'R', 200246 => 'S', 203250 => 'T', 205218 => 'W',
206244 => 'X', 209185 => 'Y', 212209 => 'Z'
);
private $_charset = 'utf-8';
public function __construct($charset = 'utf-8') {
$this->_charset = $charset;
}
// 公開介面:取得字串首字母
public function getInitials($str) {
if (empty($str)) return '';
// 純 ASCII 字串直接返回
if ($this->_isAscii($str[0]) && $this->_isAsciis($str)) {
return $str;
}
// UTF-8 轉 GB2312 處理
if ($this->_charset == 'utf-8') {
$str = iconv('utf-8', 'gb2312', $str);
}
$words = $this->_cutWord($str);
$result = array();
foreach ($words as $word) {
if ($this->_isAscii($word)) { // 英數字
$result[] = $word;
continue;
}
// 計算 GB2312 編碼值
$code = ord(substr($word, 0, 1)) * 1000 + ord(substr($word, 1, 1));
// 二分法查找
if (($idx = $this->_search($code)) !== -1) {
$result[] = $this->_pinyins[$idx];
}
}
return strtoupper(implode('', $result));
}
// 私有方法:字串切割為單字陣列(漢字=2位元組,英數=1位元組)
private function _cutWord($str) {
$words = array();
while ($str != '') {
if ($this->_isAscii($str)) {
$words[] = $str[0];
$str = substr($str, 1);
} else {
$words[] = substr($str, 0, 2);
$str = substr($str, 2);
}
}
return $words;
}
private function _isAscii($char) {
return ord(substr($char, 0, 1)) < 160;
}
private function _isAsciis($str) {
$len = min(3, strlen($str));
for ($i = 0; $i < $len; $i++) {
if (!$this->_isAscii($str[$i])) return false;
}
return true;
}
// 二分法查找核心
private function _search($code) {
$data = array_keys($this->_pinyins);
$lower = 0;
$upper = count($data) - 1;
if ($code < $data[0]) return -1;
while ($lower <= $upper) {
$mid = (int)(($lower + $upper) / 2);
if ($data[$mid] < $code) {
$lower = $mid + 1;
} elseif ($data[$mid] == $code) {
return $data[$mid];
} else {
$upper = $mid - 1;
}
}
// 找不到精確匹配,返回小於 code 的最大節點
return ($lower > 0) ? $data[$lower - 1] : -1;
}
}
// 使用範例
$py = new ChinesePinyin('utf-8');
echo $py->getInitials('啊吧才的餓飛就好i就看了嗎你哦平去人是他uv我想一在');
// 輸出:ABCDEFGHIJKLMNOPQRSTUVWXYZ
?>
| 維度 | 區間對照法 | 二分法查找版 |
|---|---|---|
| 程式碼複雜度 | 低(單一函數) | 中(類別封裝) |
| 擴充性 | 難(需手動維護區間) | 強(修改對照表即可) |
| 邊界字準確度 | 需仔細測試邊界 | 高(二分法自動定位) |
| 編碼支援 | GB2312 為主 | UTF-8/GB2312 雙支援 |
| 適用場景 | 簡單專案、效能極致要求 | 正式產品、需長期維護 |
overtrue/pinyin(基於詞庫,支援多音字、分詞)composer require overtrue/pinyinmb_detect_encoding() 判斷再轉碼獲取漢字首字母是中文系統的基礎功能。區間對照法適合快速原型、簡單需求;二分法查找版結構更清晰、擴充性更強,適合正式專案。隨著專案發展,建議遷移至成熟的 Composer 套件,獲得更完整的多音字、分詞支援,專注於核心業務邏輯開發。