以下源码基于 PHP 7.3.8

array array_flip ( array $array )

(PHP 4, PHP 5, PHP 7)

array_flip — 交换数组中的键和值

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

/* {{{ proto array array_flip(array input)
   Return array with key <-> value flipped */
PHP_FUNCTION(array_flip)
{
// 定义变量
    zval *array, *entry, data;
    zend_ulong num_idx;
    zend_string *str_idx; // 解析数组参数
    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ARRAY(array)
    ZEND_PARSE_PARAMETERS_END(); // 初始化返回数组
    array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array))); // 遍历每个元素,并执行键值交换操作
    ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
        ZVAL_DEREF(entry);
        if (Z_TYPE_P(entry) == IS_LONG) {
            if (str_idx) {
                ZVAL_STR_COPY(&data, str_idx);
            } else {
                ZVAL_LONG(&data, num_idx);
            }
            zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry), &data);
        } else if (Z_TYPE_P(entry) == IS_STRING) {
            if (str_idx) {
                ZVAL_STR_COPY(&data, str_idx);
            } else {
                ZVAL_LONG(&data, num_idx);
            }
            zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR_P(entry), &data);
        } else {
            php_error_docref(NULL, E_WARNING, "Can only flip STRING and INTEGER values!");
        }
    } ZEND_HASH_FOREACH_END();
}
/* }}} */

参数解析 Z_PARAM_ARRAY

先看参数解析部分

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(array)
ZEND_PARSE_PARAMETERS_END();

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

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

返回值 return_value

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

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

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

Z_ARRVAL_P 的定义如下:

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

zend_hash_num_elements 函数代码如下:

#define zend_hash_num_elements(ht) \
(ht)->nNumOfElements

array_init_size 函数代码如下:

#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 宏定义的内容如下:

#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \
    ZEND_HASH_FOREACH(ht, 0); \
    _h = _p->h; \
    _key = _p->key; \
    _val = _z;

继续展开 ZEND_HASH_FOREACH

#define ZEND_HASH_FOREACH(_ht, indirect) do { \
        HashTable *__ht = (_ht); \
        Bucket *_p = __ht->arData; \
        Bucket *_end = _p + __ht->nNumUsed; \
        for (; _p != _end; _p++) { \
            zval *_z = &_p->val; \
            if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \
                _z = Z_INDIRECT_P(_z); \
            } \
            if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;

ZEND_HASH_FOREACH_END 的定义如下:

#define ZEND_HASH_FOREACH_END() \
        } \
    } while (0)

ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
// code
}

完全展开如下:

do {
Bucket *_p = (_ht)->arData; // Z_ARRVAL_P(array) ---> ht ---> _ht
Bucket *_end = _p + (_ht)->nNumUsed; // 起始地址+偏移地址
for (; _p != _end; _p++) {
zval *_z = &_p->val;
if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) {
_z = Z_INDIRECT_P(_z);
}
if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;
_h = _p->h; // zend_ulong num_idx ---> _h
_key = _p->key; // zend_string *str_idx ---> _key
_val = _z; // zval *entry ---> _val
{
//code
}
}
} while (0)

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

  • 如果数组元素的索引为数字:
if (Z_TYPE_P(entry) == IS_LONG) {
if (str_idx) {
ZVAL_STR_COPY(&data, str_idx);
} else {
ZVAL_LONG(&data, num_idx);
}
zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_P(entry), &data);
}

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 函数将值插入/更新到返回数组中。

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

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

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

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


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

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

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

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

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

//运行开始
$startTime = getMicrotime();
$startMemory = getUseMemory(); $arr = [1,2,3...]; // 数据略 array_unique($arr);
// array_flip(array_flip($arr)); //运行结束
$endTime = getMicrotime();
$endMemory = getUseMemory(); //运行结果
echo "执行耗时:" . ($endTime - $startTime) * 1000 . '毫秒';
echo "占用内存:" . ($endMemory - $startMemory) . 'kb'; /**
* 获取时间(微秒)
*/
function getMicrotime(){
list($usec, $sec) = explode(' ', microtime());
return (float)$usec + (float)$sec;
} /**
* 获取使用内存(kb)
*/
function getUseMemory(){
$useMemory = round(memory_get_usage(true) / 1024, 2);
return $useMemory;
}

注:代码在终端执行: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

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

最新文章

  1. OHSCE_V0.1.22 Beta,跨平台高可靠性通信框架
  2. 尝试打开或创建物理文件 REATE FILE 遇到操作系统错误 5(拒绝访问)
  3. IIS中查看W3P.exe进程对应的应用程序池的方法
  4. codeforces 45C C. Dancing Lessons STL
  5. Python核心编程(切片索引的更多内容)
  6. AFNetworking vs ASIHTTPRequest vs MKNetworkKit
  7. codeforces 682C Alyona and the Tree DFS
  8. OWIN and Katana - 1
  9. 一起来说 Vim 语
  10. C#执行cmd命令
  11. CDN公共资源
  12. 03.DataStructure
  13. Emacs中的代码折叠控制
  14. 2018-12-14 JavaScript实现ZLOGO: 前进方向和速度
  15. jquery源码 整体架构
  16. C语言里有没有像C++里面的sort函数一样的函数?有!
  17. pycharm 如何设置函数调用字体颜色
  18. sencha touch 自定义cardpanel控件 模仿改进NavigationView 灵活添加按钮组,导航栏,自由隐藏返回按钮(废弃 仅参考)
  19. nginx 跨域解决
  20. 提交到开源git时出现:fatal: refusing to merge unrelated histories的解决办法

热门文章

  1. codeforces 794 C. Naming Company(贪心)
  2. 牛客网 湖南大学2018年第十四届程序设计竞赛重现赛 A game
  3. 线段树模板 hdu 1166 敌兵布阵
  4. 使用dig/nslookup命令查看dns解析详情
  5. SpringBoot+SpringMVC+MyBatis快速整合搭建
  6. Java中存储金额用什么数据类型
  7. 【UEFI】---BIOS中UserPassword的重复校验总结
  8. docker-将自己的Linux打包为镜像
  9. 实验吧CTF练习题---WEB---因缺思汀的绕过解析
  10. linux环境上anaconda的安装与卸载