← PHP uasort函数教程:用自定义比较函数按值排序数组并保留键名 PHP array_udiff函数:PHP数组差集运算与自定义回调比较 →

PHP array_udiff_assoc函数教程:用自定义回调同时比较键名与值求差集

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

一、array_udiff_assoc的定位

array_udiff_assoc()用来计算多个数组的差集:返回第一个数组中存在、但其他数组里不存在的元素。它有两个比较维度——键名和值,但两者的比较方式不同:

  • 键名:用PHP内置规则比较,也就是普通的==判断,不做类型转换之外的处理;

  • 值:用用户自定义的回调函数比较。

这个函数从PHP5.0开始提供。名字拆开看更好理解:array表示数组操作,u表示用户自定义函数,diff表示求差集,assoc表示比较时考虑键名关联。合起来就是“带键名比较、值用自定义规则”的差集函数。

语法:

array_udiff_assoc(array $array1, array $array2, [, array $...], callable $value_compare_func): array

参数说明:

参数 说明 是否必填
array1 被比较的主数组 必填
array2 与第一个数组比较的数组 必填
array3... 继续参与比较的数组 可选
value_compare_func 用户自定义的值比较函数 必填

这里有一个原始资料中值得纠正的地方:参数表把array2标为必填是对的,但把value_compare_func写在参数列表末尾、又标为必填,容易让人误以为它是可选参数。实际调用时,回调函数必须提供,否则函数无法确定值的比较规则。回调函数在语法签名中位于之后,但它是必填项,这一点在写代码时要特别注意。

返回值:返回一个数组,包含array1中那些在其他数组里找不到对应键值对的元素。键名会保留。

二、比较逻辑的两个层面

理解array_udiff_assoc()的关键,是把它拆成两层来看。

第一层:键名比较

键名比较用的是PHP内置规则。也就是说,"a"和"a"视为同一个键,1和"1"在关联数组键名的处理上也归为同一个键。只有键名相同,两个元素才有资格进入值比较环节。

第二层:值比较

值比较交给用户自定义函数。回调接收两个值$x和$y,返回值规则和usort()、uasort()一致:

  • $x===$y或$x==$y时返回0,表示两个值相等;

  • $x>$y返回1;

  • $x<$y返回-1。

需要说明的是,array_udiff_assoc()只关心回调返回的是不是0。返回0就认为值相等,非0就认为值不相等。所以示例里有些函数写成return1;,有些写成return($x>$y)?1:-1;,在差集判断的结果上是一样的。不过从代码规范角度看,返回-1、0、1三态更清晰,也方便复用到其他排序函数。

三、代码号学习编程实例

示例1:笔记本品牌差集

<?php
function compareLaptop($x, $y)
{
    if ($x === $y) {
        return 0;
    }
    return ($x > $y) ? 1 : -1;
}

$lapTop1 = ["a" => "dell", "b" => "hp", "c" => "asus"];
$laptop2 = ["a" => "dell", "b" => "asus", "c" => "hp"];

$result = array_udiff_assoc($lapTop1, $laptop2, "compareLaptop");
print_r($result);

输出:

Array
(
    [b] => hp
    [c] => asus
)

分析一下:键a在两个数组里都是dell,值相等,被排除。键b在第一个数组里是hp,第二个数组里是asus,值不同,保留。键c在第一个数组里是asus,第二个数组里是hp,值不同,保留。所以结果是b=>hp和c=>asus。

示例2:球类项目差集

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

$game1 = ["a" => "cricket", "b" => "cricket", "c" => "football"];
$game2 = ["a" => "cricket", "b" => "football", "c" => "cricket"];

print_r(array_udiff_assoc($game1, $game2, "compareGame"));

输出:

Array
(
    [b] => cricket
    [c] => football
)

这个示例说明了一个容易忽略的点:值相同但键不同的元素,不会被当成同一个元素。$game1["b"]是cricket,$game2["b"]是football,键相同、值不同,保留。$game1["c"]是football,$game2["c"]是cricket,同样保留。如果只按值比较、不看键,结果会不同,那是array_udiff()的职责。

示例3:板球运动员名单差集

<?php
function comparePlayer($y, $z)
{
    return ($y === $z) ? 0 : 1;
}

$array1 = [
    "a" => "sachin",
    "b" => "sehwag",
    "d" => "rahul",
    "r" => "virat"
];

$array2 = [
    "a" => "sachin",
    "y" => "sehwag",
    "d" => "rahul",
    "x" => "yuvraj"
];

$compare = array_udiff_assoc($array1, $array2, "comparePlayer");
print_r($compare);

输出:

Array
(
    [b] => sehwag
    [r] => virat
)

键a和d在两个数组里键名、值都一致,被排除。键b在$array2中不存在,保留。键r在$array2中也不存在,保留。键y和x只存在于$array2,不会出现在结果里,因为差集的方向是“从$array1出发”。

示例4:编程语言差集

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

$a1 = ["a" => "php", "b" => "java", "c" => "python"];
$a2 = ["a" => "php", "b" => "python", "c" => "java"];

print_r(array_udiff_assoc($a1, $a2, "compareLang"));

输出:

Array
(
    [b] => java
    [c] => python
)

这里回调用的是==而不是===。对于字符串值,两者结果一致;但如果值里混有数字和数字字符串,==会把"1"和1视为相等,而===不会。选择哪种,取决于业务上是否允许类型宽松比较。

四、和几个相似函数的区别

PHP里求差集的函数不止一个,名字也接近,放在一起对比更清楚:

函数 键名比较方式 值比较方式
array_diff_assoc() 内置规则 内置规则
array_udiff_assoc() 内置规则 用户自定义
array_udiff() 不比较键名 用户自定义
array_udiff_uassoc() 用户自定义 用户自定义

选择思路:

  • 键名和值都用默认规则,用array_diff_assoc();

  • 键名用默认规则、值要自定义,用array_udiff_assoc();

  • 只看值、不看键名,用array_udiff();

  • 键名和值都要自定义,用array_udiff_uassoc()。

我在项目里见过一种情况:同事想按自定义规则比较对象数组,直接用了array_udiff_assoc(),结果发现键名对不上,差集算出来是空数组。原因是他以为这个函数会忽略键名,实际上它恰恰是“带键名比较”的版本。键名不一致的元素,连进入值比较的机会都没有。这个坑值得记一下。

五、项目实战经验

1.配置项差异对比

做多环境配置同步时,经常要找出“测试环境有、生产环境没有”或者“值不一致”的配置项。配置数组通常是关联数组,键是配置名,值是配置内容,正好适合array_udiff_assoc():

<?php
$testConfig = [
    "db_host" => "127.0.0.1",
    "db_port" => "3306",
    "cache"   => "redis"
];

$prodConfig = [
    "db_host" => "10.0.0.5",
    "db_port" => "3306",
    "cache"   => "redis"
];

$diff = array_udiff_assoc($testConfig, $prodConfig, function ($a, $b) {
    return $a === $b ? 0 : 1;
});

print_r($diff);

输出:

Array
(
    [db_host] => 127.0.0.1
)

db_port和cache键值都一致,被排除;db_host键相同但值不同,保留。这个用法比手写循环加if判断要简洁。

2.用户权限列表比对

权限系统里,用户权限常用["模块名"=>"权限级别"]的结构。判断某个用户的权限和角色默认权限有哪些差异,可以这样写:

<?php
$userPerm = [
    "order"  => "read",
    "user"   => "write",
    "report" => "read"
];

$rolePerm = [
    "order"  => "read",
    "user"   => "read",
    "report" => "read"
];

$extra = array_udiff_assoc($userPerm, $rolePerm, function ($a, $b) {
    return strcmp($a, $b);
});

print_r($extra);

输出:

Array
(
    [user] => write
)

这里回调用了strcmp(),它对字符串比较返回三态值,符合比较函数的要求。用strcmp()的好处是代码短,而且大小写敏感的行为明确。如果业务需要大小写不敏感,换成strcasecmp()即可。

3.回调返回值的坑

前面提到,array_udiff_assoc()只判断回调是否返回0。但有一个细节:PHP会把回调返回值转换为整数。如果返回null、false这类值,会被当成0,从而误判为“值相等”。所以回调里不要写return$a==$b;这种布尔返回,虽然false转成0看起来“碰巧能用”,但语义是错的,容易在维护时误导别人。明确写return$a==$b?0:1;更稳妥。

4.大数组的性能考虑

array_udiff_assoc()内部需要对多个数组做比较,复杂度不低。如果数组规模到几万条以上,且回调里还有复杂逻辑,性能会明显下降。我遇到过一次接口响应变慢,之后定位到是对一个上万条的关联数组做差集,回调里还做了字符串处理。优化方式是把比较逻辑前置,先对数组做一次键值规范化,再用简单回调比较,或者改用哈希表方式自己实现差集。函数本身没问题,问题在于用在了不合适的规模上。

5.键名类型要留意

PHP数组的键名有自动转换规则:数字字符串键名会被转成整数。比如"1"作为键名,实际存储时是整数1。如果两个数组一个用"1"、一个用1作键,它们在键名比较时会被视为同一个键。这个行为在大多数情况下不影响结果,但如果业务上确实需要区分字符串键和整数键,就要提前把键名处理成统一格式,避免出现意外匹配。

六、本节课程知识要点

  • array_udiff_assoc()求差集,方向是从第一个数组出发。

  • 键名用内置规则比较,值用用户自定义回调比较。

  • 回调函数是必填参数,返回值中0表示值相等,非0表示不等。

  • 返回值保留原数组的键名。

  • 键名不一致的元素不会进入值比较环节。

  • 与array_udiff()、array_diff_assoc()、array_udiff_uassoc()的区别在于键名和值的比较方式组合不同。

  • 回调返回布尔值会被转换为整数,建议明确返回-1、0、1三态。

  • 大数组场景要评估性能,必要时改用哈希表方式自行实现。

  • PHP数组键名有自动类型转换,数字字符串键会变成整数键。

七、常见问题

问:array_udiff_assoc和array_diff_assoc有什么区别?

array_diff_assoc()的键名和值都用PHP内置规则比较;array_udiff_assoc()的键名用内置规则、值用用户自定义回调。需要自定义值比较逻辑时用后者。

问:回调函数可以只返回0和1吗?

可以。差集判断只关心是否返回0,返回1和返回-1在结果上没有区别。但为了代码可读性和复用性,建议返回完整三态。

问:这个函数会修改原数组吗?

不会。它返回一个新数组,原数组保持不变。

问:多个数组比较时,顺序有影响吗?

有。差集的方向是“第一个数组中有、其他数组中没有”。第一个数组的位置不能随意调换,否则结果变。

问:键名相同但值类型不同,会被视为相等吗?

取决于回调怎么写。用==比较时,"1"和1会被视为相等;用===则不会。需要根据业务需求选择。

array_udiff_assoc()的核心是“键名按默认规则、值按自定义规则”求差集。用它的前提是数据结构本身是关联数组,且业务上确实需要同时考虑键名和值。如果只需要按值比较,用array_udiff()更合适;如果键名和值都要自定义,则应该看array_udiff_uassoc()。项目里常见的配置比对、权限比对场景,用这个函数能省掉一层手写循环。真正需要注意的是回调返回值规范、数组规模带来的性能问题,以及PHP键名自动转换这个底层行为。把这几点处理好,函数用起来是可靠的。

← PHP uasort函数教程:用自定义比较函数按值排序数组并保留键名 PHP array_udiff函数:PHP数组差集运算与自定义回调比较 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号