若是让你用一句话描述PHP函数array_diff_uassoc
,也许你开口就来了,就是同时比较两个或多个函数,并返回在第一个函数出现且没有在其余函数出现的键值同时相同的数据。php
最近看到一个颇有意思的问题,问的是关于array_diff_uassoc
执行阅读这个问题才明白对这个函数的误解有多深。数组
下面是问题的简化版本:函数
function comparekey($a,$b){ return 0; } $array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4]; $array2 = ['a'=>2,'d'=>4,'e'=>6]; $res = array_diff_uassoc($array1,$array2,'comparekey'); var_dump($res);
为何结果是指针
['a'=>1,'c'=>3,'d'=>4];
按正常逻辑,array_diff_uassoc
返回key不同,且值不同的数组数据。自定义比较函数返回0则认为key值同样。因此正常逻辑应该返回的是code
['a'=>1,'b'=>2,'c'=>3]
其实,说实话,一开始我也是这么认为的。直到我在自定义函数中分别输出a,b,看到那奇葩的输出内容才以为,那个比较函数没那么简单。排序
为了方便看出内容,使用下面的数组替代问题中的数组内容ci
function comparekey($a,$b){ echo $a.'-'.$b; return 0; } $array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4]; $array2 = ['e'=>'2','f'=>5,'g'=>6]; $res = array_diff_uassoc($array1,$array2,'comparekey');
函数输出内容为源码
a-b b-c c-d e-f f-g a-e b-e c-e d-e
因此能够看出来,传入自定义函数进行比较的不必定是来自不一样数组的键。还有多是相同数组的键。hash
固然不是了,这个比较函数自己是比较大小的。可是却不是咱们理解的比较键值是否相等的。根据自定的返回结果,php内部会对内部的指针位置进行调整,因此咱们看到后面的比较是a-e b-e c-e d-eio
这个也不是的。实际上就是由于比较函数的数组结果回影响到php内部数组指针位置的变动。变动方式不一样会致使最终相互比价的不是咱们认为的相同键名的值相互比较。
看一下php源码,array_diff_uassoc最终都是经过php_array_diff
函数实现的。
static void php_array_diff(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp) { ... if (hash->nNumOfElements > 1) { if (behavior == DIFF_NORMAL) { zend_sort((void *) lists[i], hash->nNumOfElements, sizeof(Bucket), diff_data_compare_func, (swap_func_t)zend_hash_bucket_swap); } else if (behavior & DIFF_ASSOC) { /* triggered also when DIFF_KEY */ zend_sort((void *) lists[i], hash->nNumOfElements, sizeof(Bucket), diff_key_compare_func, (swap_func_t)zend_hash_bucket_swap); } } ... }
能够看到diff_key_compare_func
传给了排序函数。因此,自定义函数的返回结果会影响到临时变量lists
的输出。
php内部首先对全部的输入数组进行进行排序。因此在自定义函数中能够看出前面的输出内容都是先把数组的键名依次进行比较。
当输入的数组的都按键名拍好序以后,就要对第一个数组分别于其余数组的键名进行比较。
1) 比较第一个数组当前元素的键名与要比较数组的各个元素健名是否同样,知道遇到第一个同样或者比较结束为止。
RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0]))); while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) { for (i = 1; i < arr_argc; i++) { Bucket *ptr = ptrs[i]; if (behavior == DIFF_NORMAL) { while (Z_TYPE(ptrs[i]->val) != IS_UNDEF && (0 < (c = diff_data_compare_func(ptrs[0], ptrs[i])))) { ptrs[i]++; } } else if (behavior & DIFF_ASSOC) { /* triggered also when DIFF_KEY */ while (Z_TYPE(ptr->val) != IS_UNDEF && (0 != (c = diff_key_compare_func(ptrs[0], ptr)))) { ptr++; } } ... } ... }
2) 若是键名同样(健名比较函数返回0),则比较键值是否相等。若是不相等,则c设置为-1,继续比较下一个数组的元素。
RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0]))); while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) { ... for (i = 1; i < arr_argc; i++) { ... if (!c) { ... if (diff_data_compare_func(ptrs[0], ptr) != 0) { c = -1; if (key_compare_type == DIFF_COMP_KEY_USER) { BG(user_compare_fci) = *fci_key; BG(user_compare_fci_cache) = *fci_key_cache; } } ... } ... } ... }
3) 根据比较结果,若是比较结果不相等,则用第一个数组的下一个元素比较其余数组的全部元素。
若是比较结果相等(c=0),则删除返回数组(第一个数组复制获得的)对应的键名。
RETVAL_ARR(zend_array_dup(Z_ARRVAL(args[0]))); while (Z_TYPE(ptrs[0]->val) != IS_UNDEF) { ... if (!c) { for (;;) { p = ptrs[0]; p = ptrs[0]; if (p->key == NULL) { zend_hash_index_del(Z_ARRVAL_P(return_value), p->h); } else { zend_hash_del(Z_ARRVAL_P(return_value), p->key); } if (Z_TYPE((++ptrs[0])->val) == IS_UNDEF) { goto out; } ... } } else { for (;;) { if (Z_TYPE((++ptrs[0])->val) == IS_UNDEF) { goto out; } ... } ... } ... }
如下列数组以及自定义函数为例说明比较过程。
function comparekey($a,$b){ return 0; } $array1 = ['a'=>1,'b'=>2,'c'=>3,'d'=>4]; $array2 = ['a'=>2,'d'=>4,'e'=>6];
设置返回数组未array1
比较健名"a","a"相等,则比较array1['a']!=$array2['a']。
比较健名"b","a",相等,则比较array1['b']==$array2['a'],删除返回数组的键值'b'
比较健名"c","a",相等,则比较array1['c']!=$array2['a']。
比较健名"d","a",相等,则比较array1['c']!=$array2['a']。
因此最终返回数组为
$res = ['a'=>1,'c'=>3,'d'=>4]
因此,自定义函数并非让咱们彻底的自定义。自定义的函数返回结果回致使不同的输出结果。php数组有不少提供自定义的函数方法。可是,若是你的自定义函数返回值是“有悖常理的”,好比这个问题中的函数,永远都是相等的,可是php同一个数组的键值不可能相同,因此这个自定义函数的比较结果实际上是"有问题的"。在这个前提下,那么php返回的结果也有可能会有意外的输出。
当你下次使用array_diff_uassoc
函数的时候,应该了解到,这个自定义函数并不单单是比较两个数组的健名是否同样,还会影响到比较以前php对输入数组的内部排序;自定义函数的返回结果会直接影响到php数组指针的变动顺序,致使比较结果的不同;
文章首发于公众【写PHP的老王】 PS:分享不易,若是以为有用,记得分享哟