以下源码基于 PHP 7.3.8

array array_flip ( array $array )

(PHP 4, PHP 5, PHP 7)

array_flip — 交换数组中的键和值

array_flip 函数的源代码在 /ext/standard/array.c 文件中。

  1. /* {{{ proto array array_flip(array input)
  2.    Return array with key <-> value flipped */
  3. PHP_FUNCTION(array_flip)
  4. {
  5. // 定义变量
  6.     zval *array, *entry, data;
  7.     zend_ulong num_idx;
  8.     zend_string *str_idx;
  9. // 解析数组参数
  10.     ZEND_PARSE_PARAMETERS_START(1, 1)
  11.         Z_PARAM_ARRAY(array)
  12.     ZEND_PARSE_PARAMETERS_END();
  13. // 初始化返回数组
  14.     array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array)));
  15. // 遍历每个元素,并执行键值交换操作
  16.     ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
  17.         ZVAL_DEREF(entry);
  18.         if (Z_TYPE_P(entry) == IS_LONG) {
  19.             if (str_idx) {
  20.                 ZVAL_STR_COPY(&data, str_idx);
  21.             } else {
  22.                 ZVAL_LONG(&data, num_idx);
  23.             }
  24.             zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry), &data);
  25.         } else if (Z_TYPE_P(entry) == IS_STRING) {
  26.             if (str_idx) {
  27.                 ZVAL_STR_COPY(&data, str_idx);
  28.             } else {
  29.                 ZVAL_LONG(&data, num_idx);
  30.             }
  31.             zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR_P(entry), &data);
  32.         } else {
  33.             php_error_docref(NULL, E_WARNING, "Can only flip STRING and INTEGER values!");
  34.         }
  35.     } ZEND_HASH_FOREACH_END();
  36. }
  37. /* }}} */

参数解析 Z_PARAM_ARRAY

先看参数解析部分

  1. ZEND_PARSE_PARAMETERS_START(1, 1)
  2. Z_PARAM_ARRAY(array)
  3. ZEND_PARSE_PARAMETERS_END();

Z_PARAM_ARRAY 的主要作用是指定一个参数使数组解析为 zval。关于它的详细资料可以点此查看

Specify a parameter that should parsed as an array into a zval.

返回值 return_value

解析完参数后,返回数组就被初始化了:

  1. array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array)));

ZEND_FUNCTION 本身不像 PHP 一样用 return 返回值,而是修改 return_value 指针所指向的变量,内核会把 return_value 指向的变量作为用户端调用此函数后得到的返回值。

Z_ARRVAL_P 的定义如下:

  1. #define Z_ARRVAL_P(zval_p)          Z_ARRVAL(*(zval_p))

zend_hash_num_elements 函数代码如下:

  1. #define zend_hash_num_elements(ht) \
  2. (ht)->nNumOfElements

array_init_size 函数代码如下:

  1. #define array_init_size(arg, size)  ZVAL_ARR((arg), zend_new_array(size))

返回数组的初始化主要分为 3 步:

Z_ARRVAL_P 宏从 zval 里面提取值到哈希表;

zend_hash_num_elements 提取哈希表元素的个数(nNumOfElements 属性)。

array_init_size 使用 size 变量初始化数组。

键值交换

ZEND_HASH_FOREACH_KEY_VAL 宏定义的内容如下:

  1. #define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \
  2.     ZEND_HASH_FOREACH(ht, 0); \
  3.     _h = _p->h; \
  4.     _key = _p->key; \
  5.     _val = _z;

继续展开 ZEND_HASH_FOREACH

  1. #define ZEND_HASH_FOREACH(_ht, indirect) do { \
  2.         HashTable *__ht = (_ht); \
  3.         Bucket *_p = __ht->arData; \
  4.         Bucket *_end = _p + __ht->nNumUsed; \
  5.         for (; _p != _end; _p++) { \
  6.             zval *_z = &_p->val; \
  7.             if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \
  8.                 _z = Z_INDIRECT_P(_z); \
  9.             } \
  10.             if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;

ZEND_HASH_FOREACH_END 的定义如下:

  1. #define ZEND_HASH_FOREACH_END() \
  2.         } \
  3.     } while (0)

  1. ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
  2. // code
  3. }

完全展开如下:

  1. do {
  2. Bucket *_p = (_ht)->arData; // Z_ARRVAL_P(array) ---> ht ---> _ht
  3. Bucket *_end = _p + (_ht)->nNumUsed; // 起始地址+偏移地址
  4. for (; _p != _end; _p++) {
  5. zval *_z = &_p->val;
  6. if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) {
  7. _z = Z_INDIRECT_P(_z);
  8. }
  9. if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;
  10. _h = _p->h; // zend_ulong num_idx ---> _h
  11. _key = _p->key; // zend_string *str_idx ---> _key
  12. _val = _z; // zval *entry ---> _val
  13. {
  14. //code
  15. }
  16. }
  17. } while (0)

主要作用是迭代一个哈希表的键和值。在上面完全展开的代码中,省略的代码 code 主要实现交换键值

  • 如果数组元素的索引为数字:
  1. if (Z_TYPE_P(entry) == IS_LONG) {
  2. if (str_idx) {
  3. ZVAL_STR_COPY(&data, str_idx);
  4. } else {
  5. ZVAL_LONG(&data, num_idx);
  6. }
  7. zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry), &data);
  8. }

zend_hash_index_update 的三个参数分别是:需要更新的哈希表 Z_ARRVAL_P(return_value),整型下标 Z_LVAL_P(entry),值 &data

如果str_idx 不为空,就将 str_idx 拷贝给 data ,反之将 num_idx 拷贝给 data ,然后使用 zend_hash_index_update 函数将值插入/更新到返回数组中。

  • 如果数组元素的索引为字符串:
  1. else if (Z_TYPE_P(entry) == IS_STRING) {
  2. if (str_idx) {
  3. ZVAL_STR_COPY(&data, str_idx);
  4. } else {
  5. ZVAL_LONG(&data, num_idx);
  6. }
  7. zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR_P(entry), &data);
  8. }

如果str_idx 不为空,就将 str_idx 拷贝给 data ,反之将 num_idx 拷贝给 data ,然后使用 zend_symtable_update 函数将值插入/更新到返回数组中。

  • 数组元素的值只能为字符串或整数,否则报 warning 错误:
  1. else {
  2. php_error_docref(NULL, E_WARNING, "Can only flip STRING and INTEGER values!");
  3. }

以上就是 array_flip 函数的源码分析。(END)


后记:其实一开始的标题是『为什么array_flip(array_flip())比array_unique()快』,于是有了以下的篇幅☟,再然后觉得要追根溯源,于是去研究 PHP7 的源代码,标题改成了『PHP7源码解释为什么array_flip(array_flip())比array_unique()快』,就有了上边的篇幅☝,可没想到光一个 array_flip 函数的源码整理就用去了不少时间,遂定为『PHP7源码之array_flip函数』,等后面得了时间再整理 array_unique 函数的笔记。(捂脸)

今天在项目中看到这样一句代码

  1. $userIds = array_flip(array_flip($ids));

显而易见,这是为了去重,因为 array_flip 函数可以交换数组中的键和值,原来重复的值会变为相同的键。再进行一次键值互换,把键和值换回来则可以完成去重。

想起几年前跟朋友学 PHP 时,朋友说去重函数 array_unique 性能不高,要少用。只不过那时是初学,没有刨根问底。可今天不忙,就亲自动手测试了一下,简易代码如下:

  1. //运行开始
  2. $startTime = getMicrotime();
  3. $startMemory = getUseMemory();
  4. $arr = [1,2,3...]; // 数据略
  5. array_unique($arr);
  6. // array_flip(array_flip($arr));
  7. //运行结束
  8. $endTime = getMicrotime();
  9. $endMemory = getUseMemory();
  10. //运行结果
  11. echo "执行耗时:" . ($endTime - $startTime) * 1000 . '毫秒';
  12. echo "占用内存:" . ($endMemory - $startMemory) . 'kb';
  13. /**
  14. * 获取时间(微秒)
  15. */
  16. function getMicrotime(){
  17. list($usec, $sec) = explode(' ', microtime());
  18. return (float)$usec + (float)$sec;
  19. }
  20. /**
  21. * 获取使用内存(kb)
  22. */
  23. function getUseMemory(){
  24. $useMemory = round(memory_get_usage(true) / 1024, 2);
  25. return $useMemory;
  26. }

注:代码在终端执行:CentOS 7.4,PHP 7.3.4。

1w个元素,15个重复元素:

array_unique 0.84280967712402 ms 0.95009803771973 ms 0.85306167602539 ms 0.90694427490234 ms 0.87213516235352 ms
0 kb 0 kb 0 kb 0 kb 0 kb
array_flip 0.7328987121582 ms 0.74005126953125 ms 0.76198577880859 ms 0.77080726623535 ms 0.79989433288574 ms
0 kb 0 kb 0 kb 0 kb 0 kb

可以看到 array_unique 函数去重确实比 array_flip 函数所用时间长一些,但差异不大。

如果是10w个元素,10个重复元素:

array_unique 15.263795852661 ms 23.360013961792 ms 15.237092971802 ms 15.599012374878 ms 15.784978866577 ms
0 kb 0 kb 0 kb 0 kb 0 kb
array_flip 10.167121887207 ms 10.363101959229 ms 10.868072509766 ms 10.629892349243 ms 10.660171508789 ms
0 kb 0 kb 0 kb 0 kb 0 kb

可以看到两个函数的耗时拉开了差距。相信随着数据量的增大,耗时的差距也会更大。

PHP7源码之array_flip函数分析的更多相关文章

  1. PHP7源码之array_unique函数分析

    以下源码基于 PHP 7.3.8 array array_unique ( array $array [, int $sort_flags = SORT_STRING ] ) (PHP 4 >= ...

  2. keyring源码加密解密函数分析

    Encrypt the page data contents. Page type can't be FIL_PAGE_ENCRYPTED, FIL_PAGE_COMPRESSED_AND_ENCRY ...

  3. 搭建LNAMP环境(六)- PHP7源码安装MongoDB和MongoDB拓展

    上一篇:搭建LNAMP环境(五)- PHP7源码安装Redis和Redis拓展 一.安装MongoDB 1.创建mongodb用户组和用户 groupadd mongodb useradd -r -g ...

  4. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

  5. Netty 源码学习——客户端流程分析

    Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...

  6. 搭建LNAMP环境(七)- PHP7源码安装Memcached和Memcache拓展

    上一篇:搭建LNAMP环境(六)- PHP7源码安装MongoDB和MongoDB拓展 一.安装Memcached 1.yum安装libevent事件触发管理器 yum -y install libe ...

  7. 搭建LNAMP环境(五)- PHP7源码安装Redis和Redis拓展

    上一篇:搭建LNAMP环境(四)- 源码安装PHP7 一.安装Redis 1.创建redis用户组和用户 groupadd redis useradd -r -g redis -s /sbin/nol ...

  8. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  9. 读zepto源码之工具函数

    读zepto源码之工具函数 Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目标对象的属性.目 ...

随机推荐

  1. vim 同时操作多行

    使用 vim 的时候,经常会有同时注释或解开注释的情况,逐行编辑很浪费时间,下面的同时操作多行的方式 删除操作 control+v 进入 visual block 模式 选中要删除几行文字 d删除 插 ...

  2. R语言基础入门

    请先安装好R和RStudio 如果不干别的,控制台就是一个内置计算器 2 * 3 #=> 6 sqrt(36) #=> 6, square root log10(100) #=> 2 ...

  3. JS-特效 ~ 02. 屏幕滚动事件、 DTD、scroll、顶部悬浮导航、两侧跟随广告、返回顶部小火煎

    ceil 向上取整 floor 向下取整 round 四舍五入 缓动动画 动画原理 = 盒子位置 + 步长 清除定时器 步长越来越小 盒子位置 = 盒子本身位置 + (目标位置-本身位置)/n(最好为 ...

  4. spark与mapreduce的区别

    spark是通过借鉴Hadoop mapreduce发展而来,继承了其分布式并行计算的优点,并改进了mapreduce明显的缺陷,具体表现在以下几方面: 1.spark把中间计算结果存放在内存中,减少 ...

  5. 如何从 if-else 的参数校验中解放出来?

    背景 在开发中经常需要写一些字段校验的代码,比如非空,长度限制,邮箱格式验证等等,导致充满了if-else 的代码,不仅相当冗长,而且很让人抓狂. hibernate validator(官方文档)提 ...

  6. 锁和synchronized

    锁的常见概念 互斥: 同一时刻只有一个线程执行 临界区:一段需要互斥执行的代码 细粒度锁: 用不同的锁对受保护资源进行精细化管理. 细粒度锁可以提高并行度,是性能优化的一个重要手段 死锁 :一组互相竞 ...

  7. CountDownLatch、CyclicBarrier和Semaphore使用

    CountDownLatch CountDownLatch是用来线程计数的.等待一组线程全部执行完后再本线程继续执行.如:A线程需要等待B.C和D(由初始化CountDownLatch参数觉得等待多少 ...

  8. Winform中实现ZedGraph新增自定义Y轴上下限、颜色、标题功能

    场景 Winform中实现ZedGraph的多条Y轴(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1001322 ...

  9. HTML-css样式引用方式

    1.使用行内样式表 语法:在标签内部写入一个style属性. 优点:没有样式表文件,在某些时候可以提高效率: 优先级高. 缺点:多个页面难以共享样式,不利于代码复用: HTML和CSS代码混杂,不利于 ...

  10. linux 假死分析

    所谓假死,就是能ping通,但是ssh不上去:任何其他操作也都没反应,包括上面部署的apache也打不开页面. 作为一个多任务操作系统,要把系统忙死,忙到ssh都连不上去,也不是那么容易的.尤其是现在 ...