一、先搞清楚sort()到底哪里“不自然”
PHP里对数组排序的函数不少,sort()是出场率较高的一个。它按什么排?按ASCII值逐字符比较。
拿文件名来说:
$files = ["code12.php", "code22.php", "code2.php", "code3.php", "code1.php"];
sort($files);
print_r($files);
sort()的结果是:
code1.php
code12.php
code2.php
code22.php
code3.php
人眼一看就觉得别扭。code12.php排在code2.php前面,因为sort()比较到第二个字符时,'1'的ASCII值小于'2',它根本不管后面还有没有数字。这就是“字典序”和“人类阅读顺序”之间的差距。
natsort()就是来解决这个问题的。
$files = ["code12.php", "code22.php", "code2.php", "code3.php", "code1.php"];
natsort($files);
print_r($files);
输出:
[4] => code1.php
[2] => code2.php
[3] => code3.php
[0] => code12.php
[1] => code22.php
数字部分被当作数值来比:1<2<3<12<22。这才符合直觉。
二、natsort()的函数原型与参数
bool natsort(array &$array);
参数只有一个,就是待排序的数组,而且必须是变量(引用传递),不能直接传一个字面量数组。返回值是布尔型:成功true,失败false。
这里有个容易被忽略的点:natsort()会保留原来的键值对关系。上面例子里输出的是[4]=>code1.php,键还是原来的4,不是重新从0开始编号。这一点和sort()不同——sort()会丢弃原键,重新建立0、1、2的索引。
如果你排完序后还需要用原来的键去取数据,比如关联数组,那natsort()的这个特性就很关键。
三、natsort()和natcasesort()的区别
natsort()是区分大小写的。大写字母在小写字母之前,这一点和常规字符串比较一致。
$langs = ["Java8", "php5", "Python3", "ruby2", "android7"];
natsort($langs);
print_r($langs);
输出顺序大致是Java8、Python3、android7、php5、ruby2——因为大写J、P排在小写a、p、r前面。
如果不想让大小写干扰排序,用natcasesort():
$langs = ["Java8", "php5", "Python3", "ruby2", "android7"];
natcasesort($langs);
print_r($langs);
这时候就是按字母顺序忽略大小写来排:android7、Java8、php5、Python3、ruby2。
两个函数都保留键值关联,区别只在大小写敏感度。做文件列表、代码号列表这类场景,我个人更倾向natcasesort(),因为用户不会在意大小写,他们只希望“看起来有序”。
四、一个更贴近实际的例子:图片序列
假设你有一组图片,命名是img1.png、img2.png、img10.png、img12.png。
$images = ["img12.png", "img10.png", "img2.png", "img1.png"];
natsort($images);
print_r($images);
结果:
[3] => img1.png
[2] => img2.png
[1] => img10.png
[0] => img12.png
如果换成sort(),img10.png和img12.png会跑到img2.png前面。做过相册或图集的人应该都踩过这个坑。
再比如代码号学习编程时常见的任务编号:
$tasks = ["task3", "task20", "task1", "task15"];
natsort($tasks);
print_r($tasks);
输出:
[2] => task1
[0] => task3
[3] => task15
[1] => task20
数字按实际大小排,前缀task保持不变。这种排序在后台管理系统的列表展示里很实用。
五、本节课程知识要点
-
natsort()用于自然排序,把字符串中的数字段按数值比较,而不是逐字符比较。
-
保留键值关联,这是它和sort()的核心差异之一;排序后键不会重排。
-
参数是引用传递,必须传变量,不能直接传数组字面量。
-
区分大小写,需要忽略大小写时用natcasesort()。
-
返回值是布尔型,成功true,失败false,排序结果直接体现在原数组上。
-
适用场景:文件名、版本号、代码号、任务编号、图片序列等带数字的字符串排序。
-
不适用场景:纯数字数组用sort()更直接;需要按值重新索引时sort()更合适;多维数组排序要考虑usort()或array_multisort()。
六、几个实际使用中的注意点
键值保留带来的遍历问题。natsort()之后如果用for循环按0到count-1去取,可能取不到值,因为键不是连续的。用foreach更稳妥。
natsort($files);
foreach ($files as $key => $value) {
echo $key . " => " . $value . "\n";
}
natsort()不是稳定排序。如果两个元素自然排序后“相等”,它们原来的先后顺序不保证。PHP8.0之后排序稳定性有所改善,但依赖这一点不如自己写明确的比较规则。
性能上不用太担心。对几百上千个元素,natsort()和sort()的差距在日常场景里可以忽略。真正要留意的是数据量很大时,自然排序的比较逻辑比普通字符串比较稍慢,但通常不是瓶颈。
和strnatcmp()的关系。natsort()底层用的就是自然比较算法,PHP也提供了strnatcmp()和strnatcasecmp()两个函数,可以在usort()里自定义更复杂的排序规则。如果你需要多条件排序,比如先按类型再按编号,usort()配合strnatcmp()会更灵活。
usort($files, function ($a, $b) {
return strnatcmp($a, $b);
});
不过usort()不保留键值,这是取舍点。
七、小结
natsort()解决的是一个很具体的问题:字符串里的数字被人眼读取时,应该按数值大小排,而不是按字符排。它的引用传参、键值保留、大小写敏感这几个特性,决定了它适合什么场景、不适合什么场景。
写代码时遇到“code2排在code12后面”这种别扭事,先想想是不是排序函数选错了。多数时候,换成natsort()或natcasesort()就能解决,不需要自己写复杂的比较回调。