首先查看asort函数先找到其实现文件,PHP的源码中所有的标准库函数都定义在ext/standard目录中,asort其功能为array所使用,所以我们在array.c中果然找到了此方法的实现
PHP_FUNCTION(asort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &array, &sort_type) == FAILURE) {
RETURN_FALSE;
}
cmp = php_get_data_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
- 首先定义了zval实现的array指针,并定义了sort_type为PHP_SORT_REGULAR,这个变量在后面比较类型判断的时候使用;
- 又调用了zend_parse_parameters,这个函数就不在这里解析了,这个函数的实现结果是获取调用函数的传入参数,第一个参数是array,也就是我们需要排序的数组地址,我们可以看到其实有个传入参数sort_type,这里需要注意下这个传入参数.
- 接下来就是php_get_data_compare_func,这个函数是用作根据传入的sort_type来获取要使用的比较方法,我们来分析下这个方法
static compare_func_t php_get_data_compare_func(zend_long sort_type, int reverse) /* {{{ */
{
switch (sort_type & ~PHP_SORT_FLAG_CASE) {
case PHP_SORT_NUMERIC:
if (reverse) {
return php_array_reverse_data_compare_numeric;
} else {
return php_array_data_compare_numeric;
}
break;
case PHP_SORT_STRING:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_data_compare_string_case;
} else {
return php_array_data_compare_string_case;
}
} else {
if (reverse) {
return php_array_reverse_data_compare_string;
} else {
return php_array_data_compare_string;
}
}
break;
case PHP_SORT_NATURAL:
if (sort_type & PHP_SORT_FLAG_CASE) {
if (reverse) {
return php_array_reverse_natural_case_compare;
} else {
return php_array_natural_case_compare;
}
} else {
if (reverse) {
return php_array_reverse_natural_compare;
} else {
return php_array_natural_compare;
}
}
break;
#if HAVE_STRCOLL
case PHP_SORT_LOCALE_STRING:
if (reverse) {
return php_array_reverse_data_compare_string_locale;
} else {
return php_array_data_compare_string_locale;
}
break;
#endif
case PHP_SORT_REGULAR:
default:
if (reverse) {
return php_array_reverse_data_compare;
} else {
return php_array_data_compare;
}
break;
}
return NULL;
}
此篇文章我们只比较常规情况下PHP_SORT_REGULAR类型下的复杂度
- 如上函数实现,我们可以看到我们asort情况也就是升序下调用的是php_array_data_compare,如果是arsort降序下是php_array_reverse_data_compare。
cmp = php_get_data_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 0) == FAILURE) {
RETURN_FALSE;
}
此时已经返回了我们要使用的比较函数,接下来就是调用zend_hash_sort进行最终的处理了
zend_hash_sort定义肯定就要去zend目录进行查看了,此函数定义在zend_hash.h文件中
#define zend_hash_sort(ht, compare_func, renumber) \
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber)
可以看到h文件中定义了zend_hash_sort为zend_hash_sort_ex的实现,所以我们跟踪zend_hash_sort_ex,查看zend_hash.c文件,找到zend_hash_sort_ex的实现
ZEND_API int ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, compare_func_t compar, zend_bool renumber)
{
Bucket *p;
uint32_t i, j;
IS_CONSISTENT(ht);
HT_ASSERT(GC_REFCOUNT(ht) == 1);
if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */
return SUCCESS;
}
if (ht->nNumUsed == ht->nNumOfElements) {
i = ht->nNumUsed;
} else {
for (j = 0, i = 0; j < ht->nNumUsed; j++) {
p = ht->arData + j;
if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue;
if (i != j) {
ht->arData[i] = *p;
}
i++;
}
}
sort((void *)ht->arData, i, sizeof(Bucket), compar,
(swap_func_t)(renumber? zend_hash_bucket_renum_swap :
((ht->u.flags & HASH_FLAG_PACKED) ? zend_hash_bucket_packed_swap : zend_hash_bucket_swap)));
HANDLE_BLOCK_INTERRUPTIONS();
ht->nNumUsed = i;
ht->nInternalPointer = 0;
if (renumber) {
for (j = 0; j < i; j++) {
p = ht->arData + j;
p->h = j;
if (p->key) {
zend_string_release(p->key);
p->key = NULL;
}
}
ht->nNextFreeElement = i;
}
if (ht->u.flags & HASH_FLAG_PACKED) {
if (!renumber) {
zend_hash_packed_to_hash(ht);
}
} else {
if (renumber) {
void *new_data, *old_data = HT_GET_DATA_ADDR(ht);
Bucket *old_buckets = ht->arData;
new_data = pemalloc(HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), (ht->u.flags & HASH_FLAG_PERSISTENT));
ht->u.flags |= HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS;
ht->nTableMask = HT_MIN_MASK;
HT_SET_DATA_ADDR(ht, new_data);
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);
pefree(old_data, ht->u.flags & HASH_FLAG_PERSISTENT & HASH_FLAG_PERSISTENT);
HT_HASH_RESET_PACKED(ht);
} else {
zend_hash_rehash(ht);
}
}
HANDLE_UNBLOCK_INTERRUPTIONS();
return SUCCESS;
}
我们只分析此实现中最重要的一句
sort((void *)ht->arData, i, sizeof(Bucket), compar,
(swap_func_t)(renumber? zend_hash_bucket_renum_swap :
((ht->u.flags & HASH_FLAG_PACKED) ? zend_hash_bucket_packed_swap : zend_hash_bucket_swap)));
我们可以看到这里使用sort去进行排序,sort为此函数体中第二个传入的参数,sort就是在h文件中传入的zend_sort,我们进入zend_sort(zend_sort.c定义)进行分析
ZEND_API void zend_sort(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp)
{
while (1) {
if (nmemb <= 16) {
zend_insert_sort(base, nmemb, siz, cmp, swp);
return;
} else {
char *i, *j;
char *start = (char *)base;
char *end = start + (nmemb * siz);
size_t offset = (nmemb >> Z_L(1));
char *pivot = start + (offset * siz);
if ((nmemb >> Z_L(10))) {
size_t delta = (offset >> Z_L(1)) * siz;
zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp);
} else {
zend_sort_3(start, pivot, end - siz, cmp, swp);
}
swp(start + siz, pivot);
pivot = start + siz;
i = pivot + siz;
j = end - siz;
while (1) {
while (cmp(pivot, i) > 0) {
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
while (cmp(j, pivot) > 0) {
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
}
swp(i, j);
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
done:
swp(pivot, i - siz);
if ((i - siz) - start < end - i) {
zend_sort(start, (i - start)/siz - 1, siz, cmp, swp);
base = i;
nmemb = (end - i)/siz;
} else {
zend_sort(i, (end - i)/siz, siz, cmp, swp);
nmemb = (i - start)/siz - 1;
}
}
}
}
- 4-7行进行了判断, 如果元素总数 <= 16 ,则进行插入排序,这是很正常的,因为插入排序在最好的情况下时O(n) 级的算法,而数据量小的情况下,数据有序的可能性就越高,也就符合了最好的情况
- 后面就是 如果元素总数 > 16, 则开始了另外一种排序,首先确定了头元素和尾元素,并让元素总数右移一位作为基准, 这里涉及一个函数 Z_L ,后面做补充解释
- 继续判断 nmemb >> Z_L(10) 如果 元素总数 右移 10位,依然大于 0的话,选取的偏移数 再向右偏移 1位 作为 delta, 然后 zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp); 进行交换,否则 执行 zend_sort_3(start, pivot, end - siz, cmp, swp); 执行排序
- 下面就是标准的快速排序的操作思路了,只是一般我们会使用递归来处理,但是递归会消耗空间,所以 PHP源码里面选择了非递归的方式
最后看一下zend_sort_3的实现
static inline void zend_sort_3(void *a, void *b, void *c, compare_func_t cmp, swap_func_t swp) /* {{{ */ {
if (!(cmp(a, b) > 0)) {
if (!(cmp(b, c) > 0)) {
return;
}
swp(b, c);
if (cmp(a, b) > 0) {
swp(a, b);
}
return;
}
if (!(cmp(c, b) > 0)) {
swp(a, c);
return;
}
swp(a, b);
if (cmp(b, c) > 0) {
swp(b, c);
}
}
总结
php的asort函数在元素数 较小(16个及以下)的时候,使用插入排序,否则使用非递归的快速排序来进行排序