以下源碼基於 PHP 7.3.8
array array_unique ( array array[,intarray[,intsort_flags = SORT_STRING ] ) (PHP 4 >= 4.0.1, PHP 5, PHP 7) array_unique — 移除數組中重復的值 參數說明: array:輸入的數組。 sort_flag:(可選)排序類型標記,用於修改排序行為,主要有以下值: SORT_REGULAR - 按照通常方法比較(不修改類型) SORT_NUMERIC - 按照數字形式比較 SORT_STRING - 按照字元串形式比較 SORT_LOCALE_STRING - 根據當前的本地化設置,按照字元串比較。
array_unique 函數的源代碼在 /ext/standard/array.c 文件中。由於篇幅過長,完整代碼不在這里貼出來了,可以參見 GitHub 貼出的源代碼。
定義變數
首先是定義變數,array_unique 函數默認使用 PHP_SORT_STRING 排序,PHP_SORT_STRING 在 /ext/standard/php_array.h 頭文件中定義。
可以看到和開頭PHP函數的sort_flag 參數默認的預定義常量 SORT_STRING 很像。
compare_func_t cmp 這行代碼沒看懂,不清楚是做什麼的。compare_func_t 在 /Zend/zend_types.h 中定義:應該是定義了一個指向int 型返回值且帶有兩個指針常量參數的函數指針類型,沒有查到相關資料,先擱著,繼續往下看。
參數解析
ZEND_PARSE_PARAMETERS_START(1, 2),第一個參數表示必傳參數個數,第二個參數表示最多參數個數,即該函數參數范圍是 1-2 個。
數組元素個數判斷
這段代碼很容易看懂,當數組為空或只有 1 個元素時,無需去重操作,直接將array 拷貝到新數組 return_value來返回即可。
分配持久化內存
這一步只有當sort_type 為 PHP_SORT_STRING 時才執行。在下面可以看到調用 zend_hash_init 初始化了 array,調用 zend_hash_destroy 釋放持久化的內存。
設置比較函數
進行具體比較順序控制的函數指針是cmp,是通過向 php_get_data_compare_func 傳入 sort_type 和 0 得到的,sort_type 也就是 SORT_STRING 這樣的標記。
php_get_data_compare_func 在 array.c 文件中定義(即與 array_unique 函數同一文件),代碼過長,這里只貼出默認標記為 SORT_STRING 的代碼:
在前面的代碼中,我們可以看到,cmp = php_get_data_compare_func(sort_type, 0); 的第二個參數,即參數 reverse 的值為 0,也就是當 sort_type 為 PHP_SORT_STRING 時,調用的是 php_array_data_compare_string 函數,即 SORT_STRING 採用 php_array_data_compare_string 進行比較。繼續展開 php_array_data_compare_string 函數:
可以得到這樣一條調用鏈:
string_compare_function 是一個 ZEND API,在 /Zend/zend_operators.c 中定義:
可以看到,SORT_STRING 使用 zend_binary_strcmp 函數進行字元串比較。下面的代碼是 zend_binary_strcmp 的實現(也在 /Zend/zend_operators.c 中):
上面的代碼是比較兩個字元串。也就是SORT_STRING 排序方式的底層實現是 C 語言的 memcmp,即它對兩個字元串從前往後,按照逐個位元組比較,一旦位元組有差異,就終止並比較出大小。
數組排序
這段代碼初始化一個新的數組,然後將值拷貝到新數組,然後調用zend_sort 排序函數對數組進行排序。排序演算法在 /Zend/zend_sort.c 中實現,注釋有這樣一句話:
Derived from LLVM's libc++ implementation of std::sort.
這個排序演算法是基於LLVM 的 libc++ 中的 std::sort 實現的,算是快排的優化版,當元素數小於等於16時有特殊的優化,當元素數小於等於 5 時直接通過 if else 嵌套判斷排序。代碼就不貼出來了。
數組去重
回到array_unique 上,繼續看代碼:
遍歷排序好的數組,然後刪除重復的元素。
眾周所知,快排的時間復雜度是O(nlogn),因此,array_unique 函數的時間復雜度是O(nlogn)。array_unique 底層調用了快排演算法,加大了函數運行的時間開銷,當數據量很大時,會導致整個函數的運行較慢。