一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - PHP教程 - PHP中array_keys和array_unique函數源碼的分析

PHP中array_keys和array_unique函數源碼的分析

2020-12-22 18:16PHP教程網 PHP教程

本文從array_keys和array_unique的源碼分析出函數的性能,并給出了優化建議,十分不錯的文章,有需要的小伙伴可以參考下

性能分析

從運行性能上分析,看看下面的測試代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$test=array();
for($run=0; $run<10000; $run++)
$test[]=rand(0,100);
 
$time=microtime(true);
 
$out = array_unique($test);
 
$time=microtime(true)-$time;
echo 'Array Unique: '.$time."\n";
 
$time=microtime(true);
 
$out=array_keys(array_flip($test));
 
$time=microtime(true)-$time;
echo 'Keys Flip: '.$time."\n";
 
$time=microtime(true);
 
$out=array_flip(array_flip($test));
 
$time=microtime(true)-$time;
echo 'Flip Flip: '.$time."\n";

運行結果如下:

 

從上圖可以看到,使用array_unique函數需要0.069s;使用array_flip后再使用array_keys函數需要0.00152s;使用兩次array_flip函數需要0.00146s。

測試結果表明,使用array_flip后再調用array_keys函數比array_unique函數快。那么,具體原因是什么呢?讓我們看看在PHP底層,這兩個函數是怎么實現的。

源碼分析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* {{{ proto array array_keys(array input [, mixed search_value[, bool strict]])
  Return just the keys from the input array, optionally only for the specified       search_value */
PHP_FUNCTION(array_keys)
{
  //變量定義
  zval *input,        /* Input array */
     *search_value = NULL,  /* Value to search for */
     **entry,        /* An entry in the input array */
      res,          /* Result of comparison */
     *new_val;        /* New value */
  int  add_key;        /* Flag to indicate whether a key should be added */
  char *string_key;      /* String key */
  uint  string_key_len;
  ulong num_key;        /* Numeric key */
  zend_bool strict = 0;    /* do strict comparison */
  HashPosition pos;
  int (*is_equal_func)(zval *, zval *, zval * TSRMLS_DC) = is_equal_function;
 
  //程序解析參數
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|zb", &input, &search_value, &strict) == FAILURE) {
    return;
  }
 
  // 如果strict是true,則設置is_equal_func為is_identical_function,即全等比較
  if (strict) {
    is_equal_func = is_identical_function;
  }
 
  /* 根據search_vale初始化返回的數組大小 */
  if (search_value != NULL) {
    array_init(return_value);
  } else {
    array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(input)));
  }
  add_key = 1;
 
  /* 遍歷輸入的數組參數,然后添加鍵值到返回的數組 */
  zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(input), &pos);//重置指針
  //循環遍歷數組
  while (zend_hash_get_current_data_ex(Z_ARRVAL_P(input), (void **)&entry, &pos) == SUCCESS) {
    // 如果search_value不為空
    if (search_value != NULL) {
      // 判斷search_value與當前的值是否相同,并將比較結果保存到add_key變量
      is_equal_func(&res, search_value, *entry TSRMLS_CC);
      add_key = zval_is_true(&res);
    }
 
    if (add_key) {
      // 創建一個zval結構體
      MAKE_STD_ZVAL(new_val);
 
      // 根據鍵值是字符串還是整型數字將值插入到return_value中
      switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(input), &string_key, &string_key_len, &num_key, 1, &pos)) {
        case HASH_KEY_IS_STRING:
          ZVAL_STRINGL(new_val, string_key, string_key_len - 1, 0);
          // 此函數負責將值插入到return_value中,如果鍵值已存在,則使用新值更新對應的值,否則直接插入
          zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
          break;
 
        case HASH_KEY_IS_LONG:
          Z_TYPE_P(new_val) = IS_LONG;
          Z_LVAL_P(new_val) = num_key;
          zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &new_val, sizeof(zval *), NULL);
          break;
      }
    }
 
    // 移動到下一個
    zend_hash_move_forward_ex(Z_ARRVAL_P(input), &pos);
  }
}
/* }}} */

以上是array_keys函數底層的源碼。為方便理解,筆者添加了一些中文注釋。如果需要查看原始代碼,可以點擊查看。這個函數的功能就是新建一個臨時數組,然后將鍵值對重新復制到新的數組,如果復制過程中有重復的鍵值出現,那么就用新的值替換。這個函數的主要步驟是地57和63行調用的zend_hash_next_index_insert函數。該函數將元素插入到數組中,如果出現重復的值,則使用新的值更新原鍵值指向的值,否則直接插入,時間復雜度是O(n)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* {{{ proto array array_flip(array input)
  Return array with key <-> value flipped */
PHP_FUNCTION(array_flip)
{
  // 定義變量
  zval *array, **entry, *data;
  char *string_key;
  uint str_key_len;
  ulong num_key;
  HashPosition pos;
 
  // 解析數組參數
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &array) == FAILURE) {
    return;
  }
 
  // 初始化返回數組
  array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array)));
 
  // 重置指針
  zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos);
  // 遍歷每個元素,并執行鍵<->值交換操作
  while (zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **)&entry, &pos) == SUCCESS) {
    // 初始化一個結構體
    MAKE_STD_ZVAL(data);
    // 將原數組的值賦值為新數組的鍵
    switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(array), &string_key, &str_key_len, &num_key, 1, &pos)) {
      case HASH_KEY_IS_STRING:
        ZVAL_STRINGL(data, string_key, str_key_len - 1, 0);
        break;
      case HASH_KEY_IS_LONG:
        Z_TYPE_P(data) = IS_LONG;
        Z_LVAL_P(data) = num_key;
        break;
    }
 
    // 將原數組的鍵賦值為新數組的值,如果有重復的,則使用新值覆蓋舊值
    if (Z_TYPE_PP(entry) == IS_LONG) {
      zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_PP(entry), &data, sizeof(data), NULL);
    } else if (Z_TYPE_PP(entry) == IS_STRING) {
      zend_symtable_update(Z_ARRVAL_P(return_value), Z_STRVAL_PP(entry), Z_STRLEN_PP(entry) + 1, &data, sizeof(data), NULL);
    } else {
      zval_ptr_dtor(&data); /* will free also zval structure */
      php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can only flip STRING and INTEGER values!");
    }
 
    // 下一個
    zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos);
  }
}
/* }}} */

上面就是是array_flip函數的源碼。點擊鏈接查看原始代碼。這個函數主要的做的事情就是創建一個新的數組,遍歷原數組。在26行開始將原數組的值賦值為新數組的鍵,然后在37行開始將原數組的鍵賦值為新數組的值,如果有重復的,則使用新值覆蓋舊值。整個函數的時間復雜度也是O(n)。因此,使用了array_flip之后再使用array_keys的時間復雜度是O(n)。

接下來,我們看看array_unique函數的源碼。點擊鏈接查看原始代碼。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* {{{ proto array array_unique(array input [, int sort_flags])
  Removes duplicate values from array */
PHP_FUNCTION(array_unique)
{
  // 定義變量
  zval *array, *tmp;
  Bucket *p;
  struct bucketindex {
    Bucket *b;
    unsigned int i;
  };
  struct bucketindex *arTmp, *cmpdata, *lastkept;
  unsigned int i;
  long sort_type = PHP_SORT_STRING;
 
  // 解析參數
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|l", &array, &sort_type) == FAILURE) {
    return;
  }
 
  // 設置比較函數
  php_set_compare_func(sort_type TSRMLS_CC);
 
  // 初始化返回數組
  array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array)));
  // 將值拷貝到新數組
  zend_hash_copy(Z_ARRVAL_P(return_value), Z_ARRVAL_P(array), (copy_ctor_func_t) zval_add_ref, (void *)&tmp, sizeof(zval*));
 
  if (Z_ARRVAL_P(array)->nNumOfElements <= 1) {  /* 什么都不做 */
    return;
  }
 
  /* 根據target_hash buckets的指針創建數組并排序 */
  arTmp = (struct bucketindex *) pemalloc((Z_ARRVAL_P(array)->nNumOfElements + 1) * sizeof(struct bucketindex), Z_ARRVAL_P(array)->persistent);
  if (!arTmp) {
    zval_dtor(return_value);
    RETURN_FALSE;
  }
  for (i = 0, p = Z_ARRVAL_P(array)->pListHead; p; i++, p = p->pListNext) {
    arTmp[i].b = p;
    arTmp[i].i = i;
  }
  arTmp[i].b = NULL;
  // 排序
  zend_qsort((void *) arTmp, i, sizeof(struct bucketindex), php_array_data_compare TSRMLS_CC);
 
  /* 遍歷排序好的數組,然后刪除重復的元素 */
  lastkept = arTmp;
  for (cmpdata = arTmp + 1; cmpdata->b; cmpdata++) {
    if (php_array_data_compare(lastkept, cmpdata TSRMLS_CC)) {
      lastkept = cmpdata;
    } else {
      if (lastkept->i > cmpdata->i) {
        p = lastkept->b;
        lastkept = cmpdata;
      } else {
        p = cmpdata->b;
      }
      if (p->nKeyLength == 0) {
        zend_hash_index_del(Z_ARRVAL_P(return_value), p->h);
      } else {
        if (Z_ARRVAL_P(return_value) == &EG(symbol_table)) {
          zend_delete_global_variable(p->arKey, p->nKeyLength - 1 TSRMLS_CC);
        } else {
          zend_hash_quick_del(Z_ARRVAL_P(return_value), p->arKey, p->nKeyLength, p->h);
        }
      }
    }
  }
  pefree(arTmp, Z_ARRVAL_P(array)->persistent);
}
/* }}} */

可以看到,這個函數初始化一個新的數組,然后將值拷貝到新數組,然后在45行調用排序函數對數組進行排序,排序的算法是zend引擎的塊樹排序算法。接著遍歷排序好的數組,刪除重復的元素。整個函數開銷最大的地方就在調用排序函數上,而快排的時間復雜度是O(nlogn),因此,該函數的時間復雜度是O(nlogn)。

結論

因為array_unique底層調用了快排算法,加大了函數運行的時間開銷,導致整個函數的運行較慢。這就是為什么array_keys比array_unique函數更快的原因。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 福利一区在线观看 | 91会员 | 6080窝窝理论 | 欧美xingai | 蜜柚精彩在线观看 | 97精品国产自在现线免费 | 91久操| 欧美特黄三级在线观看 | 猛男强攻变sao货 | 亚洲高清国产拍精品动图 | 男人操女生 | 国内永久第一免费福利视频 | aaaa黄色片| 男同志gays | 全日爱韩国视频在线观看 | avove本人照片 | 国产精品久久久久无毒 | 精品网站 | 精品亚洲国产一区二区 | 国产视频a区 | 蜜月aⅴ免费一区二区三区 蜜桃影像传媒推广 | 久久视频精品3线视频在线观看 | 亚洲日本中文字幕天堂网 | 91小视频在线观看免费版高清 | 亚洲第一二三四区 | 日本人成年视频在线观看 | 女色在线观看免费视频 | 乌克兰肛交影视 | 大胸美女被c | 亚洲AV久久无码精品九号 | 男人好大好硬好爽免费视频 | 国产成人精品系列在线观看 | 午夜国产精品影院在线观看 | 小鸟酱在线播放 | tobu8中国在线观看免费视频 | 亚洲xxxxxhd奶水女人 | 精品一区二区三区高清免费不卡 | 五月最新女厕所高跟嘘嘘 | 全黄h全肉细节文在线观看 全彩成人18h漫画 | 小sao货水好多真紧h的视频 | 小小水蜜桃视频高清在线播放 |