← PHP array_unshift函数详解:数组头部插入元素与键名重建机制 PHP array_values函数详解:提取数组值并重建数字索引 →

PHP usort函数详解:自定义回调按值排序与键名重建机制

著
原创 2026-10-09 PHP 已有人查阅

一、函数定位与核心机制

usort是PHP内置的数组排序函数,从PHP4.0开始提供。名字里的u代表userfunction,意思是「按值排序,比较规则由用户自定义回调决定」。

当数组需要按某种非平凡(non-trivial)的标准排序时,比如按字符串长度、按对象属性、按自定义优先级,PHP内部的排序规则不够用,就得用usort把比较权交给开发者。

与相近函数的区分

先理清这几个排序函数的分工:

函数 排序对象 比较规则 是否保留键名
sort 值 内部规则 否,键名重建
asort 值 内部规则 是
usort 值 用户回调 否,键名重建
uasort 值 用户回调 是
uksort 键名 用户回调 是

usort和uasort的区别就在键名是否保留。usort会重建键名,uasort保留原键名。这个差异在关联数组排序时影响很大。

个人经验:在电商商品排序场景里,商品数组用商品ID作键名,需要按销量排序。一开始用usort,排完后商品ID键名全丢了,变成0、1、2的连续数字,后续按ID取商品全部失败。换成uasort后键名保留,问题解决。这个坑在第一次用usort时很容易中招,尤其是从sort迁移过来的开发者。

语法结构

bool usort(array &$array, callable $value_compare_func): bool

参数说明:

参数 是否必需 说明
array 必需 待排序的数组,传引用,函数直接修改原数组
value_compare_func 必需 用户自定义的值比较函数

返回值是布尔值:成功返回true,失败返回false。排序结果直接体现在传入的数组变量上,这一点和uksort一致。

回调返回值语义

回调函数接收两个元素值作为参数,返回整数:

  • 第一个参数小于第二个参数,返回负数

  • 两个参数相等,返回0

  • 第一个参数大于第二个参数,返回正数

原文示例里有个值得注意的地方:return($a>$b)?-1:1;返回的是-1和1,把「大于」映射成-1,实现的是降序排序。返回值的正负决定排序方向,这是理解usort的关键。

用一张对照表说明:

回调返回逻辑 排序结果
($a>$b)?1:-1 升序
($a>$b)?-1:1 降序
$a<=>$b 升序
$b<=>$a 降序

PHP7之后推荐用太空船操作符<=>,它直接返回-1、0、1,比三元表达式简洁。

二、键名重建机制

usort会丢弃原有键名,把结果数组重新索引成从0开始的连续整数。这一点和array_unshift的数值键名重建类似,但usort是全部重建,不区分字符串键名还是数字键名。

$country = array("d" => "denmark", "a" => "australia", "b" => "bangladesh");
usort($country, "compare");
print_r($country);

输出:

Array
(
    [0] => denmark
    [1] => bangladesh
    [2] => australia
)

原来的键名d、a、b全部丢失,变成0、1、2。

如果业务上需要保留键名,得用uasort。这一点在关联数组排序时必须先想清楚:排序后的结果是要按新顺序遍历,还是要按原键名访问。前者用usort,后者用uasort。

三、回调编写要点

回调必须对称。内部排序后,参数哪个来自前面、哪个来自后面是随机的。回调不能依赖参数顺序做逻辑分支,否则结果会漂移。

返回0的场景要处理。两个值相等时返回0,这是规范。如果漏掉这个判断,只返回1和-1,排序仍能跑,但内部算法遇到相等元素时的稳定性不如返回0。

回调里不要做副作用操作。内部排序会让回调被调用多次,调用次数和顺序不固定。在回调里写日志、改全局变量、做数据库查询,结果都不可预期。早期项目里我在回调里累加计数器想统计比较次数,结果发现同一个元素被比较了多次,计数对不上。

大数据量下注意性能。排序的时间复杂度大约是O(nlogn),回调本身的开销会被放大。如果数组有几千条以上,回调里避免做字符串拼接、正则匹配、数据库查询这类相对重的操作。必要时先把值预处理成规范形式,再排序。

比较对象属性时先取出来。如果数组元素是对象,回调里反复调用getter方增加开销。可以在排序前先把需要的属性提取到一个临时数组,排序后再映射回去。不过这样代码复杂度会上升,是否值得要看数据量。

四、项目实战示例

示例一:字符串降序排序

<?php
function codehub_cmp($a, $b) {
    if ($a == $b) {
        return 0;
    }
    return ($a > $b) ? -1 : 1;
}

$country = array("d" => "denmark", "a" => "australia", "b" => "bangladesh");
usort($country, "codehub_cmp");
print_r($country);

输出:

Array
(
    [0] => denmark
    [1] => bangladesh
    [2] => australia
)

回调里($a>$b)?-1:1表示第一个参数大时返回负数,排在前面,实现降序。键名被重建。

示例二:数字升序排序

<?php
function codehub_num($a, $b) {
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}

$numbers = array(11, 56, 8, 96);
usort($numbers, "codehub_num");
print_r($numbers);

输出:

Array
(
    [0] => 8
    [1] => 11
    [2] => 56
    [3] => 96
)

回调里($a<$b)?-1:1实现升序。这里要注意,如果不用usort而用sort,结果一样,但sort的性能更好,因为比较规则在内部用C实现。只有当排序规则确实需要业务自定义时,才用usort。

示例三:字符串按字典序升序

<?php
function codehub_str($a, $b) {
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}

$language = array('php', 'java', 'c++', 'csharp');
usort($language, "codehub_str");
print_r($language);

输出:

Array
(
    [0] => c++
    [1] => csharp
    [2] => java
    [3] => php
)

按字符串字典序排列。这里有个细节:c++和csharp的比较,+的ASCII码是43,s的ASCII码是115,所以c++排在csharp前面。如果业务上想按字母顺序排列忽略符号,需要在回调里做额外处理,比如用strcmp前先过滤掉非字母字符。

示例四:按字符串长度排序

<?php
function codehub_len($a, $b) {
    $lenA = strlen($a);
    $lenB = strlen($b);
    if ($lenA == $lenB) {
        return 0;
    }
    return ($lenA < $lenB) ? -1 : 1;
}

$news = array('aajtak', 'abp', 'zee news', 'ndtv');
usort($news, "codehub_len");
print_r($news);

输出:

Array
(
    [0] => abp
    [1] => ndtv
    [2] => aajtak
    [3] => zee news
)

这个示例展示了usort的典型用途:按字符串长度排序,这是PHP内部排序规则做不到的。abp和ndtv长度都是3和4,aajtak长度6,zeenews长度8(含空格)。

示例五:用太空船操作符简化

<?php
function codehub_spaceship($a, $b) {
    return $b <=> $a;
}

$scores = array(88, 72, 95, 60);
usort($scores, "codehub_spaceship");
print_r($scores);

输出:

Array
(
    [0] => 95
    [1] => 88
    [2] => 72
    [3] => 60
)

$b<=>$a把两个参数位置对调,等价于降序。PHP7及以上版本推荐这种写法,可读性比嵌套三元表达式好。

五、容易踩的坑

键名被重建导致关联丢失。usort会丢弃原有键名,关联数组排序后变成索引数组。如果业务上需要按原键名访问,用uasort。

返回值和排序方向的关系容易搞反。很多人以为返回1就是排在后面,实际上返回正数表示第一个参数排在第二个参数之后。降序和升序的区别就在返回值正负上,写的时候先在纸上画两个值验证一下。

传引用修改原数组。usort直接修改传入的数组,不返回新数组。如果业务上需要保留原数组,先复制一份。这个坑在多层函数调用里容易被忽略,导致上游数据被意外改动。

回调里做类型转换要谨慎。如果数组里混有字符串和数字,回调里的===会让"53"和53判为不相等。如果业务上需要把它们视为相同,得在回调里统一类型,比如都用(int)转换后再比。

失败时返回false但不报错。usort在参数类型不对时返回false,但不会抛出异常。如果不检查返回值,可能以为排序成功,实际上数组没动。写代码时很好加一层判断:

if (!usort($data, "codehub_cmp")) {
    // 处理失败情况
}

稳定性问题。PHP8.0之前usort不保证排序稳定性,相等元素的相对顺序可能改变。PHP8.0及以后版本排序算法稳定,相等元素保持原顺序。如果代码要兼容旧版本,且业务依赖相等元素的顺序,需要在回调里加上二级比较条件,比如值相等时按键名比较。

六、本节课程知识要点

  • usort按值排序,比较规则由用户回调决定

  • 回调返回负数表示第一个参数排前面,返回正数表示排后面

  • 回调返回值的正负决定排序方向

  • 第一个参数传引用,函数直接修改原数组,返回布尔值

  • 键名会被重建,从0开始连续编号,字符串键名也丢失

  • 需要保留键名时用uasort

  • PHP7及以上推荐用太空船操作符<=>简化回调

  • 简单排序优先用sort,只有非平凡排序才用usort

  • PHP8.0之前不保证排序稳定性,兼容旧版本时注意相等元素的顺序

七、适用场景

这个函数适合以下场景:

  • 按字符串长度排序

  • 按对象属性排序(比如按商品价格、按用户年龄)

  • 按自定义优先级排序(比如按状态枚举的顺序)

  • 多维数组按某个字段排序

  • 混合类型数组按业务规则排序

不适合的场景:

  • 简单的字母升序或数字升序,用sort性能更好

  • 需要保留键名的关联数组排序,用uasort

  • 按键名排序,用uksort或ksort

  • 需要保留原数组不变,先复制再排序

← PHP array_unshift函数详解:数组头部插入元素与键名重建机制 PHP array_values函数详解:提取数组值并重建数字索引 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号