CSZFC)abc123 → abc123)我i我j → WIWJ)<?php
/**
* 漢字拼音首字母工具類
* 支持 UTF-8 / GB2312 輸入
* 算法:GB2312 區位碼映射 + 二分查找
*/
class Str2Pinyin
{
/** GB2312 區位碼起始值 => 首字母映射表 */
private $_pinyins = [
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(string $charset = 'utf-8')
{
$this->_charset = strtolower($charset);
}
/** 公開入口:獲取首字母串 */
public function getInitials(string $str): string
{
if ($str === '') return '';
// 純 ASCII 快速通道
if ($this->_isAscii($str[0]) && $this->_isAsciis($str)) {
return $str;
}
// 統一轉 GB2312 查表
if ($this->_charset === 'utf-8') {
$str = iconv('UTF-8', 'GB2312//IGNORE', $str);
}
$words = $this->_cutWord($str);
$result = [];
foreach ($words as $word) {
if ($this->_isAscii($word)) {
$result[] = $word; // 英文/數字/符號保留
continue;
}
// 計算區位碼:高字節*1000 + 低字節
$code = ord($word[0]) * 1000 + ord($word[1]);
if (($idx = $this->_search($code)) !== -1) {
$result[] = $this->_pinyins[$idx];
}
}
return strtoupper(implode('', $result));
}
/* ========== 內部輔助方法 ========== */
/** 判斷單字符是否 ASCII */
private function _isAscii(string $char): bool
{
return ord($char[0]) < 160;
}
/** 判斷字符串前幾字符是否全 ASCII(避免誤判中英混合) */
private function _isAsciis(string $str): bool
{
$len = min(3, strlen($str));
for ($i = 0; $i < $len; $i++) {
if (!$this->_isAscii($str[$i])) return false;
}
return true;
}
/** 按字符切分:中文雙字節、英文單字節 */
private function _cutWord(string $str): array
{
$words = [];
while ($str !== '') {
if ($this->_isAscii($str)) {
$words[] = $str[0];
$str = substr($str, 1);
} else {
$word = $this->_msubstr($str, 0, 1); // 取一個漢字(雙字節)
$words[] = $word;
$str = substr($str, strlen($word));
}
}
return $words;
}
/** 多字節安全 substr(GB2312 環境) */
private function _msubstr(string $str, int $start, int $len): string
{
$start *= 2;
$len *= 2;
$result = '';
$strlen = strlen($str);
for ($i = 0; $i < $strlen; $i++) {
if ($i >= $start && $i < $start + $len) {
$result .= (ord($str[$i]) > 129) ? substr($str, $i++, 2) : $str[$i];
} elseif (ord($str[$i]) > 129) {
$i++; // 跳過雙字節第二字節
}
}
return $result;
}
/** 二分查找區位碼對應的拼音索引 */
private function _search(int $code): int
{
$keys = array_keys($this->_pinyins);
$low = 0;
$high = count($keys) - 1;
if ($code < $keys[0]) return -1;
while ($low <= $high) {
$mid = (int) (($low + $high) / 2);
if ($keys[$mid] < $code) {
$low = $mid + 1;
} elseif ($keys[$mid] > $code) {
$high = $mid - 1;
} else {
return $keys[$mid]; // 精確命中
}
}
// 未精確命中,返回小於 code 的最大鍵(區間查找)
return ($high >= 0) ? $keys[$high] : -1;
}
}
<?php
$py = new Str2Pinyin();
// 純中文
echo $py->getInitials('測試字符串'); // CSZFC
// 純英文
echo $py->getInitials('abc123'); // abc123
// 中英混合
echo $py->getInitials('我i我j'); // WIWJ
// 長句子
echo $py->getInitials('啊吧才的餓飛就好i就看了嗎你哦平去人是他uv我想一在');
// ABCDEFGHIJKLMNOPQRSTUVWXYZ (演示全字母覆蓋)
// 指定 GB2312 輸入
$pyGbk = new Str2Pinyin('gb2312');
echo $pyGbk->getInitials('測試'); // CS
overtrue/pinyin 等成熟庫,基於詞庫分詞獲取完整拼音。iconv 轉換。U+4E00-U+9FFF 區間)。GB2312 將漢字按拼音排序編碼,每個拼音首字母佔據一個連續區間:
| 首字母 | 區位碼範圍 | 示例漢字 |
|---|---|---|
| A | 1601-1636 | 啊 阿 埃 |
| B | 1637-1832 | 巴 白 百 |
| C | 1833-2077 | 才 材 財 |
| ... | ... | ... |
| Z | 7845-7940 | 在 再 造 |
映射表鍵值 高字節*1000 + 低字節 即區位碼(如 1601 → 1601)。二分查找定位區間,即可得到首字母。
這個類以 零依賴、極低內存、O(n log m) 時間複雜度 解決了首字母提取的核心需求。對於全拼、分詞、多音字等進階場景,建議併用 overtrue/pinyin、jiannei/laravel-pinyin 等成熟 Composer 包。理解區位碼原理,有助於在無擴展環境下快速實現定制化拼音功能。