專案常需將中文轉為拼音(如排序、搜尋、URL slug、首字母縮寫)。PHP 無內建函式,需自行實作或引用套件。本文提供自含式類別(無外部依賴),支援全拼、首字母、自定義分隔符,並解析核心原理與效能優化方向。
中文編碼中,GB2312 將常用漢字編排在 94x94 矩陣,每字佔 2 位元組。透過 ord($str[$i])*256 + ord($str[$i+1]) - 65536 計算區位碼,再查表對應拼音。此表收錄 6763 個一級漢字,覆蓋 99%+ 日常場景。
<?php
/**
* 中文轉拼音工具類(基於 GB2312 區位碼)
* 支援:全拼、首字母、自定義分隔符、長度截取
* 注意:輸入字串必須為 GB2312/GBK 編碼,或先 iconv 轉碼
*/
class GetPinyin
{
// 區位碼 -> 拼音映射表(節選,完整表含 400+ 入口)
private static $pinyinMap = [
-20319 => 'a', -20317 => 'ai', -20304 => 'an', -20295 => 'ang',
-20292 => 'ao', -20283 => 'ba', -20265 => 'bai', -20257 => 'ban',
-20242 => 'bang',-20230 => 'bao', -20051 => 'bei', -20036 => 'ben',
-20032 => 'beng',-20026 => 'bi', -20002 => 'bian',-19990 => 'biao',
-19986 => 'bie', -19982 => 'bin', -19976 => 'bing',-19805 => 'bo',
-19784 => 'bu', -19775 => 'ca', -19774 => 'cai', -19763 => 'can',
-19756 => 'cang',-19751 => 'cao', -19746 => 'ce', -19741 => 'ceng',
-19739 => 'cha', -19728 => 'chai',-19725 => 'chan',-19715 => 'chang',
-19540 => 'chao',-19531 => 'che', -19525 => 'chen',-19515 => 'cheng',
-19500 => 'chi', -19484 => 'chong',-19479 => 'chou',-19467 => 'chu',
// ... 此處省略,完整表請見原始碼或下方下載連結
-10254 => 'zuo'
];
/**
* 取得完整拼音
* @param string $chinese GB2312/GBK 編碼字串
* @param string $delimiter 分隔符,預設空字串直接連接
* @param int $length 截取長度(0=不截取)
* @return string
*/
public static function getAll(string $chinese, string $delimiter = '', int $length = 0): string
{
$result = [];
$len = strlen($chinese);
for ($i = 0; $i < $len; $i++) {
$p = ord($chinese[$i]);
if ($p > 160) { // 雙位元組中文字元
$q = ord($chinese[++$i]);
$code = $p * 256 + $q - 65536;
$result[] = self::codeToPinyin($code);
} else { // ASCII 字元直接保留
$result[] = $chinese[$i];
}
}
$py = implode($delimiter, $result);
return $length > 0 ? substr($py, 0, $length) : $py;
}
/**
* 取得首字母縮寫
* @param string $chinese
* @return string
*/
public static function getFirst(string $chinese): string
{
$result = '';
$len = strlen($chinese);
for ($i = 0; $i < $len; $i++) {
$p = ord($chinese[$i]);
if ($p > 160) {
$q = ord($chinese[++$i]);
$code = $p * 256 + $q - 65536;
$py = self::codeToPinyin($code);
$result .= $py[0] ?? ''; // 取首字母
} else {
$result .= $chinese[$i];
}
}
return $result;
}
/**
* 區位碼轉拼音(二分查找優化)
*/
private static function codeToPinyin(int $code): string
{
if ($code > 0 && $code < 160) return chr($code); // ASCII
if ($code < -20319 || $code > -10247) return ''; // 範圍外
// 映射表鍵值已按 code 由小到大排序,可用二分查找 O(log n)
$keys = array_keys(self::$pinyinMap);
$low = 0;
$high = count($keys) - 1;
$match = '';
while ($low <= $high) {
$mid = (($low + $high) >> 1);
if ($keys[$mid] <= $code) {
$match = self::$pinyinMap[$keys[$mid]];
$low = $mid + 1;
} else {
$high = $mid - 1;
}
}
return $match;
}
}
<?php
require_once 'GetPinyin.php';
// 來源字串需為 GB2312/GBK 編碼
$str = '羽泉樂隊,歌不錯-推薦';
// 若來源為 UTF-8,先轉碼
$str = iconv('UTF-8', 'GBK//IGNORE', $str);
echo '全拼: ' . GetPinyin::getAll($str, ' ') . "\n";
// 輸出: yu quan yue dui , ge bu cuo - tui jian
echo '首字母: ' . GetPinyin::getFirst($str) . "\n";
// 輸出: yqydgbc-tj
echo '無分隔全拼: ' . GetPinyin::getAll($str) . "\n";
// 輸出: yuquanyueduigebucuotuijian
| 限制 | 影響 | 解決方案 |
|---|---|---|
| 僅支援 GB2312/GBK 編碼輸入 | UTF-8 直接傳入會產生亂碼 | 先 iconv('UTF-8', 'GBK//IGNORE', $str) 轉碼 |
| 不支援多音字 | 「行」固定轉 "xing" 或 "hang" | 引入 overtrue/pinyin 等支援詞庫分詞的套件 |
| 不支援生僻字/二級漢字 | 區位碼表僅含一級 6763 字 | 同上,或補充映射表 |
| 效能:逐字迴圈 + 線性查找 | 長字串(>1萬字)明顯延遲 | 改用二分查找(上方已實作)、快取編碼轉換結果 |
composer require overtrue/pinyin
<?php
use Overtrue\Pinyin\Pinyin;
$pinyin = new Pinyin();
// 預設輸出陣列
print_r($pinyin->convert('羽泉樂隊'));
// [
// '全拼' => ['yu', 'quan', 'yue', 'dui'],
// '首字母' => ['y', 'q', 'y', 'd'],
// '簡拼' => ['yqyd'],
// ]
// 字串模式
echo $pinyin->convert('羽泉樂隊', PINYIN_ASCII); // "yu quan yue dui"
echo $pinyin->convert('羽泉樂隊', PINYIN_ASCII | PINYIN_FIRST); // "y q y d"
優勢:支援多音字(詞庫分詞)、UTF-8 原生、持續維護、效能優化(快取)。
array_keys() 重建。array_map + 單次 iconv 轉換整個陣列。