PHP 获取汉字首字母并分组排序:实现方式、调用示例与适用边界

键盘旁的猫
键盘旁的猫
发布时间:2026-08-25| 156人浏览过
原创

前言

很多列表页都会遇到“按首字母分组”的需求,比如城市选择、通讯录索引,或者 App 里的字母检索。本文把一段可直接使用的 PHP 实现重新梳理一遍:先看它怎么取中文首字母,再看如何完成二维数组分组排序,最后结合示例结果说明这种写法适合什么场景、有哪些边界需要提前心里有数。

很多列表页都会遇到“按首字母分组”的需求,比如城市选择、通讯录索引,或者 App 里的字母检索。本文把一段可直接使用的 PHP 实现重新梳理一遍:先看它怎么取中文首字母,再看如何完成二维数组分组排序,最后结合示例结果说明这种写法适合什么场景、有哪些边界需要提前心里有数。

为什么项目里经常需要首字母分组

这类需求并不少见。最典型的场景,是城市列表、地区选择器、品牌索引页,以及移动端常见的 A-Z 快速定位。用户并不关心数据在数据库里怎么存,但会很在意列表能不能按字母快速找到目标。

如果数据本身是中文,就不能直接用普通字符串排序替代“拼音首字母分组”。例如“安徽”“福建”“广东”“江苏”在展示层经常需要被整理成 A、F、G、J 这样的索引结构,前端拿到结果后,无论是网页还是 App,都能直接渲染成带字母标题的列表。

原始思路并不复杂,核心就两步:

  • 先为每一项计算一个首字母字段;
  • 再按这个字段把二维数组归组并排序。

下面这份代码正是围绕这两个步骤展开的。

核心实现:先取首字母,再完成分组排序

先看完整类。它包含 3 个方法:groupByInitials() 负责给每条数据补上首字母并触发排序,sortInitials() 负责按字母归组,getInitials() 负责把中文转换成对应的首字母。

用纸面流程卡片展示 PHP 从中文字段提取首字母并完成分组排序的处理路径
首字母分组的处理链路把取首字母、补字段、按键分组这三个步骤拆开后,这段工具类的结构会更容易读。
<?php

/**
 * @author Tech
 */
class Character
{
    /**
     * 二维数组根据首字母分组排序
     * @param  array  $data      二维数组
     * @param  string $targetKey 首字母的键名
     * @return array             根据首字母关联的二维数组
     */
    public function groupByInitials(array $data, $targetKey = 'name')
    {
        $data = array_map(function ($item) use ($targetKey) {
            return array_merge($item, [
                'initials' => $this->getInitials($item[$targetKey]),
            ]);
        }, $data);
        $data = $this->sortInitials($data);
        return $data;
    }

    /**
     * 按字母排序
     * @param  array  $data
     * @return array
     */
    public function sortInitials(array $data)
    {
        $sortData = [];
        foreach ($data as $key => $value) {
            $sortData[$value['initials']][] = $value;
        }
        ksort($sortData);
        return $sortData;
    }

    /**
     * 获取首字母
     * @param  string $str 汉字字符串
     * @return string 首字母
     */
    public function getInitials($str)
    {
        if (empty($str)) {return '';}
        $fchar = ord($str{0});
        if ($fchar >= ord('A') && $fchar <= ord('z')) {
            return strtoupper($str{0});
        }

        $s1  = iconv('UTF-8', 'gb2312', $str);
        $s2  = iconv('gb2312', 'UTF-8', $s1);
        $s   = $s2 == $str ? $s1 : $str;
        $asc = ord($s{0}) * 256 + ord($s{1}) - 65536;
        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';
        }

        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;
    }

}

这段实现的优点,是结构足够直接,放进项目就能用。只要你的二维数组里有一个中文字段,比如地区名、城市名、分类名,就可以通过 $targetKey 指定它,然后统一补充 initials 字段。

groupByInitials 做了什么

groupByInitials() 的入口很明确:接收原始二维数组,再接收一个要提取首字母的字段名。它用 array_map() 遍历每一项,把原来的数据和新的 initials 字段合并在一起。

这意味着它不会破坏你原来的业务字段,而是在原结构上补充一个可用于展示和排序的值。对于接口层来说,这种处理方式比较顺手,因为前端通常也希望保留完整记录,而不是只拿到一个字母列表。

sortInitials 怎么完成分组

sortInitials() 的逻辑也很实用。它没有对整条记录做复杂排序,而是直接把首字母作为关联数组的键:

  • $sortData['A'][] 收集所有 A 开头的数据;
  • $sortData['H'][] 收集所有 H 开头的数据;
  • 最后再通过 ksort($sortData) 按键名升序排列。

这样处理后的结果,天然就适合渲染成“字母标题 + 列表项”的结构。相比先整体排序、再遍历切段,这种写法更贴近常见接口返回格式。

getInitials 的核心原理

真正的关键在 getInitials()。如果字符串首字符本身就是英文字母,代码直接返回其大写形式:

if ($fchar >= ord('A') && $fchar <= ord('z')) {
    return strtoupper($str{0});
}

如果是中文,则先经过一次编码转换:

$s1  = iconv('UTF-8', 'gb2312', $str);
$s2  = iconv('gb2312', 'UTF-8', $s1);
$s   = $s2 == $str ? $s1 : $str;
$asc = ord($s{0}) * 256 + ord($s{1}) - 65536;

这里的思路是利用 gb2312 编码区间,把汉字映射到一个数值范围,再根据不同区间落在哪一段,返回对应的拼音首字母。例如某个区间返回 A,另一个区间返回 B,一直到 Z。

这种方式是早期 PHP 项目里很常见的方案,优点是不用额外依赖第三方库;缺点也很明显,它依赖固定的编码区间判断,因此更适合做“常见中文词条的首字母归组”,而不是做严格意义上的完整拼音转换。

怎么在项目里使用这段代码

原文给出的建议是直接引入类文件即可;如果你的项目已经使用命名空间,也可以按现有目录结构自行补充。实际使用时,只需要准备好原始数据,再指定目标字段名。

// 按首字母排序
$data = [
    ['id' => 1, 'area_name' => '山东'],
    ['id' => 2, 'area_name' => '江苏'],
    ['id' => 3, 'area_name' => '安徽'],
    ['id' => 4, 'area_name' => '福建'],
    ['id' => 5, 'area_name' => '江西'],
    ['id' => 6, 'area_name' => '广东'],
    ['id' => 7, 'area_name' => '广西'],
    ['id' => 8, 'area_name' => '海南'],
    ['id' => 9, 'area_name' => '河南'],
    ['id' => 10, 'area_name' => '湖南'],
    ['id' => 11, 'area_name' => '湖北'],
];
// 初始化,然后调用分组方法
$data = (new Character)->groupByInitials($data, 'area_name');

这里的重点有两个。第一,原始数组中的字段名并不固定,所以方法第二个参数 'area_name' 很重要,它告诉程序应该从哪个字段中提取首字母。第二,返回结果已经不是简单的顺序数组,而是“首字母 => 对应列表”的结构。

如果你处理的不是地区,而是城市、学校、品牌、联系人姓名,只要字段里存的是需要拿来分组的中文文本,这个调用方式都一样。接口层通常可以直接把结果返回给前端,前端按字母键遍历输出即可。

输出结果长什么样,为什么它适合前端展示

执行后的结果如下:

展示输入地区数组如何整理成 A、F、G、H、J、S 分组结果的暖色纸面结构图
从原始数组到字母分组结果接口返回的重点不是单纯排序,而是直接形成可供前端两层遍历的字母分组结构。
$data = array(
    'A' => array(
        0 => array(
            'id' => 3,
            'area_name' => '安徽',
            'initials' => 'A'
        )
    ) ,
    'F' => array(
        0 => array(
            'id' => 4,
            'area_name' => '福建',
            'initials' => 'F'
        )
    ) ,
    'G' => array(
        0 => array(
            'id' => 6,
            'area_name' => '广东',
            'initials' => 'G'
        ) ,
        1 => array(
            'id' => 7,
            'area_name' => '广西',
            'initials' => 'G'
        )
    ) ,
    'H' => array(
        0 => array(
            'id' => 8,
            'area_name' => '海南',
            'initials' => 'H'
        ) ,
        1 => array(
            'id' => 9,
            'area_name' => '河南',
            'initials' => 'H'
        ) ,
        2 => array(
            'id' => 10,
            'area_name' => '湖南',
            'initials' => 'H'
        ) ,
        3 => array(
            'id' => 11,
            'area_name' => '湖北',
            'initials' => 'H'
        )
    ) ,
    'J' => array(
        0 => array(
            'id' => 2,
            'area_name' => '江苏',
            'initials' => 'J'
        ) ,
        1 => array(
            'id' => 5,
            'area_name' => '江西',
            'initials' => 'J'
        )
    ) ,
    'S' => array(
        0 => array(
            'id' => 1,
            'area_name' => '山东',
            'initials' => 'S'
        )
    )
);

从展示层角度看,这个结果很清晰:

  • A 组里有“安徽”;
  • G 组里有“广东”“广西”;
  • H 组里有“海南”“河南”“湖南”“湖北”。

前端如果要做通讯录式索引,通常只需要两层循环:外层遍历字母分组,内层输出组内项目。因为每条记录都保留了原始字段和新增的 initials 字段,所以无论你是做接口缓存、模板渲染,还是给移动端返回 JSON,结构都比较顺手。

另外,ksort() 已经保证了 A-Z 的键顺序,因此前端一般不需要再单独排序一次,除非业务上还要加入“热门城市”“当前定位”这种额外分区。

这类实现的适用范围与需要注意的地方

这段代码能解决很多常规需求,但在实际项目里,仍有几个判断标准值得提前说明。

用边界说明卡片总结这类 PHP 首字母方案在编码、多音字和兼容性上的注意事项
上线前要看的几个边界这类方案胜在轻量直接,但是否适合上线,还要看数据类型、编码环境和精度要求。

适合哪些场景

如果你的目标是做首字母索引、列表归组、地区字母导航,这种写法已经够用。它轻量、直接、没有外部依赖,尤其适合老项目、工具类封装,或者只需要把中文名称按拼音首字母粗略归类的接口场景。

编码依赖要留意

getInitials() 明确使用了 iconv('UTF-8', 'gb2312', $str)。这意味着你的运行环境需要支持相应编码转换,同时数据本身也要尽量保持规范的 UTF-8 输入。否则一旦转换异常,首字母结果就可能不稳定。

多音字和生僻字不是它的强项

这种方案基于编码区间映射,并不是完整拼音词典。对于常见省份、城市、一般词条,表现通常没问题;但如果遇到多音字、人名、品牌特例或生僻字,返回结果未必符合业务预期。换句话说,它更适合“首字母归组”,不适合“高精度拼音系统”。

老写法在新 PHP 版本中的兼容性

代码里使用了 $str{0}、$s{0}、$s{1} 这种字符串下标写法。对阅读老项目代码来说这没问题,但如果你准备在较新的 PHP 环境里长期使用,通常会考虑按项目规范评估兼容性处理方式。本文保留原实现,是为了完整呈现它的原始思路和调用方法。

总体来看,这份工具类最大的价值,不是把“拼音转换”做得多复杂,而是用很短的代码,把“中文字段提取首字母 + 二维数组按字母分组排序”这件事一次解决掉。对于城市列表、地区索引、通讯录导航这类需求,它依然是一种相当务实的 PHP 写法。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。