一、函数定位与核心机制
array_uintersect_uassoc是PHP内置的数组处理函数,从PHP5.0开始提供。它是交集函数家族里校验维度最完整的一个,名字可以拆成三段来看:
-
uintersect:值(value)的比较走用户自定义回调
-
uassoc:键(key)的比较也走用户自定义回调
-
intersect:计算交集,返回array1中同时满足键值匹配的元素
和array_uintersect_assoc的区别在这里:array_uintersect_assoc的键名比较由PHP内部完成,只有值比较走自定义回调;而array_uintersect_uassoc把键名比较的控制权也交给开发者,两个维度都能自定义规则。
语法结构
array_uintersect_uassoc(
array $array1,
array $array2,
array ...$arrays,
callable $value_compare_func,
callable $key_compare_func
): array
参数说明:
| 参数 | 是否必需 | 说明 |
|---|---|---|
| array1 | 必需 | 被比较的主数组,结果从这个数组里取 |
| array2 | 必需 | 参与比较的数组 |
| ...$arrays | 可选 | 更多参与比较的数组 |
| value_compare_func | 必需 | 用户自定义的值比较函数 |
| key_compare_func | 必需 | 用户自定义的键比较函数 |
这里有个容易写反的地方:值比较回调在前,键比较回调在后。参数列表的顺序是先值后键,和函数名里uintersect在前、uassoc在后的顺序一致。实际写代码时如果调换了两个回调的位置,结果会错乱,而且不会报错,只是匹配结果不对。
比较逻辑拆解
这个函数的执行分四步:
-
PHP内部先对参与比较的数组排序,排序时分别调用键回调和值回调
-
逐个比对元素,键比较回调返回0且值比较回调也返回0,才算匹配
-
把array1中同时满足两个条件的元素挑出来
-
保留array1的原始键名,组成结果数组返回
关键点在于:两个回调都必须返回0才判定为匹配。任何一个返回非0,该元素就被排除。这一点和「与」逻辑一致,两个条件同时成立才通过。
回调返回值规范
原文里有一条重要说明值得单独拎出来:键回调和值回调都必须在第一个参数小于、等于、大于第二个参数时,分别返回小于0、等于0、大于0的整数。
这条规则是硬性要求,不是建议。回调返回0表示相等,返回正数表示第一个参数大,返回负数表示第一个参数小。如果随便返回true/false,内部排序会失去稳定性,匹配结果不可预期。
二、与相近函数的区分
交集函数家族里几个成员经常被搞混,这里做一次系统对比:
| 函数 | 键比较方式 | 值比较方式 |
|---|---|---|
| array_intersect | 不比较键 | 字符串比较 |
| array_intersect_assoc | 内部比较 | 字符串比较 |
| array_uintersect | 不比较键 | 用户回调 |
| array_uintersect_assoc | 内部比较 | 用户回调 |
| array_uintersect_uassoc | 用户回调 | 用户回调 |
从这个表能看出,array_uintersect_uassoc是灵活性较高的一档。它适合键和值都需要按业务规则比对的场景。
个人经验:在配置中心的多环境配置比对项目中,配置项的键是配置名,值是配置内容。不同环境的配置名可能大小写不一致(比如DB_HOST和db_host),配置内容里可能有前后空格。这种情况下用array_uintersect_assoc就不够,因为键名比较走的是PHP内部规则,大小写敏感。换成array_uintersect_uassoc,键比较用strcasecmp,值比较用自定义的trim后比对,问题就解决了。
三、双回调的编写规范
两个回调的签名一致,都是接收两个参数返回整数:
function codehub_key_cmp($a, $b) {
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
function codehub_value_cmp($a, $b) {
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
几个实操要点:
两个回调必须对称。内部排序后,参数哪个来自array1、哪个来自array2是随机的。回调不能依赖参数来源做逻辑分支,否则结果会随数组长度变化而漂移。
两个回调都要返回规范整数。只返回0和非0在多数情况下能跑,但内部排序算法对返回值的大小关系敏感,规范返回能让结果更稳定。
键回调处理的是键名,不是键值。键名在PHP里可能是字符串也可能是整数,回调里要注意类型。如果键名混有"1"和1,用===会判为不等,用==会判为相等。这个选择取决于业务语义。
回调里不要做副作用操作。内部排序会让回调被调用多次,且顺序不固定。在回调里写日志、改全局变量、做数据库查询,结果都不可预期。这一点我在早期项目中踩过坑:在值回调里做缓存写入,想减少重复比较,结果发现缓存键冲突,反而拖慢了整体。
性能上有额外开销。两个回调意味着比较次数比单回调函数多,内部排序的常数项更大。数据量超过几千条时,如果两个回调本身都很重,整体耗时会明显上升。个人建议:先用键名过滤缩小候选集,再用这个函数做精确比对。
四、项目实战示例
示例一:键值都需匹配的基础场景
<?php
function codehub_key($u, $v) {
if ($u === $v) {
return 0;
}
return ($u > $v) ? 1 : -1;
}
function codehub_value($u, $v) {
if ($u === $v) {
return 0;
}
return ($u > $v) ? 1 : -1;
}
$a1 = array("a" => "Apple", "b" => "Ball", "c" => "Cat");
$a2 = array("a" => "Apple", "b" => "Ball", "c" => "Dog");
$result = array_uintersect_uassoc($a1, $a2, "codehub_value", "codehub_key");
print_r($result);
输出:
Array
(
[a] => Apple
[b] => Ball
)
键名a、b、c都相同,但c的值不同(Cat和Dog),所以只有a和b被保留。注意参数顺序是值回调在前、键回调在后。
示例二:键名大小写不敏感的值交集
<?php
function codehub_key_cmp($a, $b) {
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
function codehub_value_cmp($a, $b) {
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
$a1 = array("a" => "PHP", "b" => "JAVA", "c" => "bluej");
$a2 = array("a" => "PHP", "b" => "JAVA", "c" => "JAVA");
$result = array_uintersect_uassoc($a1, $a2, "codehub_value_cmp", "codehub_key_cmp");
print_r($result);
输出:
Array
(
[a] => PHP
[b] => JAVA
)
c键的值一个是bluej一个是JAVA,不匹配,被排除。
示例三:数字字符串键名的处理
<?php
function codehub_key_num($a, $b) {
if ($a == $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
function codehub_value_num($a, $b) {
if ($a == $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
}
$arr1 = array("1" => "java", "2" => "javatpoint", "3" => "PHP");
$arr2 = array("1" => "java", "2" => "inheritance", "3" => "PHP");
$res = array_uintersect_uassoc($arr1, $arr2, "codehub_value_num", "codehub_key_num");
print_r($res);
输出:
Array
(
[1] => java
[3] => PHP
)
这个示例用的是松散比较==,键名"1"和1会被判为相等。如果换成===,"1"和1就判为不等,结果会是空数组。这个选择要看业务:如果键名来源统一,用===更安全;如果来源混杂,用==能容忍类型差异。
示例四:直接使用内置回调函数
<?php
$arr1 = array("a" => "java", "b" => "php", "c" => "pearl", "swift");
$arr2 = array("a" => "JAVA", "B" => "php", "python", "swift");
print_r(array_uintersect_uassoc($arr1, $arr2, "strcasecmp", "strcasecmp"));
输出:
Array
(
[a] => java
[b] => php
)
strcasecmp是PHP内置的大小写不敏感字符串比较函数,同时作为键回调和值回调传入。键名a和a匹配,b和B在大小写不敏感规则下也匹配。c和0不匹配,所以pearl和python被排除。swift虽然值相同,但array2中它的键名是0,array1中是0,理论上键名相同,但array2里swift的位置在python之后,索引是1而不是0,所以键名对不上,被排除。
五、容易踩的坑
回调参数顺序写反。函数签名的顺序是value_compare_func在前、key_compare_func在后。写反了不会报错,但结果不对。这一点在第一次用这个函数时很容易中招。
两个回调必须都返回0才匹配。只要有一个返回非0,元素就被排除。如果业务上想放宽条件,比如键名匹配就行、值不比较,那不该用这个函数,应该用array_intersect_assoc或array_uintersect_assoc。
键名类型转换。PHP数组里数字字符串键名"1"会被自动转成整数1。如果两个数组一个用"1"一个用1,内部存储时其实都是整数1,键回调收到的参数也是整数。这个行为在文档里写得不明显,调试时容易困惑。
空数组参与比较。只要有一个数组为空,结果一定是空数组。写代码时如果忘了判空,可能得到意料之外的结果。
性能开销。两个回调意味着比较次数比单回调函数多,内部排序的常数项更大。在数据量大的场景下,建议先用键名过滤缩小候选集,再用这个函数做精确比对。如果键或值的比较规则简单且固定,优先考虑用array_intersect_assoc或array_uintersect_assoc,没必要上双回调。
六、本节课程知识要点
-
array_uintersect_uassoc同时比对键名和键值,两个维度都走用户自定义回调
-
函数签名的回调顺序是值回调在前、键回调在后
-
两个回调都必须返回规范整数,0表示相等
-
键和值都判定为相等,元素才进入结果集
-
结果保留array1的原始键名
-
与array_uintersect_assoc的区别在于键名比较是否走自定义回调
-
大数据量下注意双回调带来的性能开销
七、适用场景
这个函数适合以下场景:
-
键名和键值都需要按业务规则比对的配置比对
-
键名大小写不敏感、值需要自定义规则的多来源数据去重
-
键名有类型差异但业务上视为相同的场景(用==做键回调)
-
需要同时控制键和值比较精度的复杂比对
不适合的场景:
-
只关心值不关心键名,用array_uintersect
-
键名比较规则固定,用array_uintersect_assoc性能更好
-
键值比较规则都简单且固定,用array_intersect_assoc更直接
-
需要保留重复元素的场景,这个函数不处理重复键