一、函数定位与核心机制
uksort是PHP内置的数组排序函数,从PHP4.0开始提供。名字里的u代表userfunction,意思是「按键名排序,比较规则由用户自定义回调决定」。
这里要区分三个容易混的函数:
-
ksort:按键名排序,比较规则由PHP内部决定
-
usort:按值排序,比较规则由用户回调决定
-
uksort:按键名排序,比较规则由用户回调决定
uksort的独特之处在于,它操作的对象是数组的键,而不是值。键名在PHP数组里可能是字符串、整数,甚至是混合类型。当业务上需要对键名做特殊排序(比如按字母逆序、按业务编码规则、按版本号顺序)时,内部比较规则不够用,就得用uksort。
语法结构
bool uksort(array &$array, callable $key_compare_func): bool
参数说明:
| 参数 | 是否必需 | 说明 |
|---|---|---|
| array | 必需 | 待排序的数组,传引用,函数直接修改原数组 |
| key_compare_func | 必需 | 用户自定义的键比较函数 |
返回值只有两种:成功返回true,失败返回false。注意这里返回的是布尔值,不是排序后的数组。排序结果直接体现在传入的数组变量上,这一点和array_uintersect系列返回新数组的行为不同。
回调返回值语义
回调函数接收两个键名作为参数,返回整数:
-
第一个参数小于第二个参数,返回负数
-
两个参数相等,返回0
-
第一个参数大于第二个参数,返回正数
原文里有个细节值得注意:示例中的回调return($a>$b)?-1:1;返回的是-1和1,也就是把「大于」映射成-1,「小于」映射成1。这实际上实现的是降序排序。返回值决定排序方向,不是固定的,这是理解uksort的关键。
传引用机制
uksort的第一个参数是&$array,传引用。函数执行后原数组被就地修改,不会产生新数组。这一点和array_uintersect返回新数组的行为形成对比。写代码时如果想把排序结果保留到另一个变量,需要先复制一份再排序:
$sorted = $original;
uksort($sorted, "codehub_cmp");
否则原数组会被直接改动,后续逻辑可能受影响。这个坑在排查数据被意外修改的问题时经常遇到。
二、排序方向的控制
回调返回值的正负决定排序方向,这是uksort里最需要理清的一点。用一个对照表说明:
| 回调返回逻辑 | 排序结果 |
|---|---|
($a>$b)?1:-1 |
升序(从小到大) |
($a>$b)?-1:1 |
降序(从大到小) |
$a<=>$b |
升序 |
$b<=>$a |
降序 |
PHP7之后引入了太空船操作符<=>,它直接返回-1、0、1,写起来比三元表达式简洁:
function codehub_cmp($a, $b) {
return $a <=> $b;
}
等价于升序排序。要降序就把两个参数换个位置:return$b<=>$a;。这是目前比较推荐的写法,可读性比嵌套三元表达式好。
三、回调编写要点
回调必须对称。内部排序后,参数哪个来自前面、哪个来自后面是随机的。回调不能依赖参数顺序做逻辑分支,否则结果会漂移。
返回0的场景要处理。两个键名相等时返回0,这是规范。如果漏掉这个判断,只返回1和-1,排序仍能跑,但内部算法遇到相等元素时的行为不如返回0稳定。
键名类型要留意。PHP数组里数字字符串键名"1"会被自动转成整数1。回调里收到的参数类型可能和字面量不一致。如果键名混有字符串和整数,用==比较会做类型转换,用===比较更严格。这个选择取决于业务语义。
回调里不要做副作用操作。内部排序会让回调被调用多次,调用次数和顺序不固定。在回调里写日志、改全局变量、做数据库查询,结果都不可预期。早期项目里我在回调里累加计数器想统计比较次数,结果发现同一个键被比较了多次,计数对不上。
大数据量下注意性能。排序的时间复杂度大约是O(nlogn),回调本身的开销会被放大。如果数组有几千条以上,回调里避免做字符串拼接、正则匹配这类相对重的操作。必要时先把键名预处理成规范形式,再排序。
四、项目实战示例
示例一:按键名降序排序
<?php
function codehub_cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a > $b) ? -1 : 1;
}
$input = array("d" => "mango", "a" => "apple", "b" => "banana");
uksort($input, "codehub_cmp");
print_r($input);
输出:
Array
(
[d] => mango
[b] => banana
[a] => apple
)
回调里($a>$b)?-1:1表示第一个参数大时返回负数,即排在前面,实现降序。结果键名按d、b、a排列。
示例二:数字键名的降序排列
<?php
function codehub_num_cmp($x, $y) {
if ($x == $y) {
return 0;
}
return ($x > $y) ? -1 : 1;
}
$language = array("1" => "java", "2" => "php", "6" => "python", "4" => "ruby");
uksort($language, "codehub_num_cmp");
print_r($language);
输出:
Array
(
[6] => python
[4] => ruby
[2] => php
[1] => java
)
键名"1"、"2"、"6"、"4"在PHP内部都转成了整数,回调收到的也是整数。降序排列后是6、4、2、1。
示例三:升序排序的写法
<?php
function codehub_asc($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$numbers = array("a" => 9, "b" => 2, "c" => 60, "d" => 78);
uksort($numbers, "codehub_asc");
print_r($numbers);
输出:
Array
(
[a] => 9
[b] => 2
[c] => 60
[d] => 78
)
回调里($a<$b)?-1:1表示第一个参数小时返回负数,排在前面,实现升序。键名a、b、c、d本身就是升序排列的,所以结果顺序不变。
示例四:用太空船操作符简化
<?php
function codehub_spaceship($x, $y) {
return $y <=> $x;
}
$sports = array("1" => "cricket", "2" => "hockey", "8" => "football", "4" => "badminton");
uksort($sports, "codehub_spaceship");
print_r($sports);
输出:
Array
(
[8] => football
[4] => badminton
[2] => hockey
[1] => cricket
)
$y<=>$x把两个参数位置对调,等价于降序。结果和示例二一致,但写法更简洁。PHP7及以上版本推荐用这种写法。
五、容易踩的坑
返回值和排序方向的关系容易搞反。很多人以为返回1就是排在后面,实际上返回正数表示第一个参数排在第二个参数之后。降序和升序的区别就在返回值正负上,写的时候先在纸上画两个键名验证一下。
传引用导致原数组被改。uksort直接修改传入的数组,不返回新数组。如果业务上需要保留原数组,先复制一份。这个坑在多层函数调用里容易被忽略,导致上游数据被意外改动。
键名类型混合时比较结果不确定。数组里同时有字符串键名和整数键名时,==比较会做类型转换,可能得到意料之外的结果。比如"1abc"==1在旧版本PHP里是true,PHP8之后行为有变化。混合类型键名的数组建议先统一键名类型,再排序。
失败时返回false但不报错。uksort在参数类型不对时返回false,但不会抛出异常。如果不检查返回值,可能以为排序成功,实际上数组没动。写代码时很好加一层判断:
if (!uksort($data, "codehub_cmp")) {
// 处理失败情况
}
性能敏感场景的取舍。如果只是按字母升序或数字升序排列键名,ksort就够了,性能比uksort好,因为比较规则在内部用C实现。只有当排序规则确实需要业务自定义时,才用uksort。这一点在做大批量数据排序时值得考虑。
六、本节课程知识要点
-
uksort按键名排序,比较规则由用户回调决定
-
回调返回负数表示第一个参数排前面,返回正数表示排后面
-
回调返回值的正负决定排序方向,降序和升序通过调换返回值实现
-
第一个参数传引用,函数直接修改原数组,返回布尔值
-
PHP7及以上推荐用太空船操作符
<=>简化回调 -
与ksort的区别在于比较规则是否自定义,与usort的区别在于排序对象是键还是值
-
大数据量下注意回调开销,简单排序优先用ksort
七、适用场景
这个函数适合以下场景:
-
键名需要按业务编码规则排序(比如订单号、版本号)
-
键名需要按字母逆序或自定义顺序排列
-
键名大小写不敏感排序(回调里用strcasecmp)
-
键名是复合结构需要拆分后比较
不适合的场景:
-
简单的字母升序或数字升序,用ksort性能更好
-
需要按值排序,用usort或uasort
-
需要保留原数组不变,先复制再排序或改用其他返回新数组的函数
-
键名类型混杂且未统一,建议先规范键名再排序