若是讓你用一句話描述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:分享不易,若是以爲有用,記得分享喲