### 7.7.6 数组操作
#### 7.7.6.1 创建数组
创建一个新的HashTable分为两步:首先是分配zend_array内存,这个可以通过`ZVAL_NEW_ARR()`宏分配,也可以自己直接分配;然后初始化数组,通过`zend_hash_init()`宏完成,如果不进行初始化数组将无法使用。
```c
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent) \
_zend_hash_init((ht), (nSize), (pDestructor), (persistent) ZEND_FILE_LINE_CC)
```
* __ht:__ 数组地址HashTable*,如果内部使用可以直接通过emalloc分配
* __nSize:__ 初始化大小,只是参考值,这个值会被对齐到2^n,最小为8
* __pHashFunction:__ 无用,设置为NULL即可
* __pDestructor:__ 删除或更新数组元素时会调用这个函数对操作的元素进行处理,比如将一个字符串插入数组,字符串的refcount增加,删除时不是简单的将元素的Bucket删除就可以了,还需要对其refcount进行处理,这个函数就是进行清理工作的
* __persistent:__ 是否持久化
示例:
```c
zval array;
uint32_t size;
ZVAL_NEW_ARR(&array);
zend_hash_init(Z_ARRVAL(array), size, NULL, ZVAL_PTR_DTOR, 0);
```
#### 7.7.6.2 插入、更新元素
数组元素的插入、更新主要有三种情况:key为zend_string、key为普通字符串、key为数值索引,相关的宏及函数:
```c
// 1) key为zend_string
//插入或更新元素,会增加key的refcount
#define zend_hash_update(ht, key, pData) \
_zend_hash_update(ht, key, pData ZEND_FILE_LINE_CC)
//插入或更新元素,当Bucket类型为indirect时,将pData更新至indirect的值,而不是更新Bucket
#define zend_hash_update_ind(ht, key, pData) \
_zend_hash_update_ind(ht, key, pData ZEND_FILE_LINE_CC)
//添加元素,与zend_hash_update()类似,不同的地方在于如果元素已经存在则不会更新
#define zend_hash_add(ht, key, pData) \
_zend_hash_add(ht, key, pData ZEND_FILE_LINE_CC)
//直接插入元素,不管key存在与否,如果存在也不覆盖原来元素,而是当做哈希冲突处理,所有会出现一个数组中key相同的情况,慎用!!!
#define zend_hash_add_new(ht, key, pData) \
_zend_hash_add_new(ht, key, pData ZEND_FILE_LINE_CC)
// 2) key为普通字符串:char*
//与上面几个对应,这里的key为普通字符串,会自动生成zend_string的key
#define zend_hash_str_update(ht, key, len, pData) \
_zend_hash_str_update(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_update_ind(ht, key, len, pData) \
_zend_hash_str_update_ind(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_add(ht, key, len, pData) \
_zend_hash_str_add(ht, key, len, pData ZEND_FILE_LINE_CC)
#define zend_hash_str_add_new(ht, key, len, pData) \
_zend_hash_str_add_new(ht, key, len, pData ZEND_FILE_LINE_CC)
// 3) key为数值索引
//插入元素,h为数值
#define zend_hash_index_add(ht, h, pData) \
_zend_hash_index_add(ht, h, pData ZEND_FILE_LINE_CC)
//与zend_hash_add_new()类似
#define zend_hash_index_add_new(ht, h, pData) \
_zend_hash_index_add_new(ht, h, pData ZEND_FILE_LINE_CC)
//更新第h个元素
#define zend_hash_index_update(ht, h, pData) \
_zend_hash_index_update(ht, h, pData ZEND_FILE_LINE_CC)
//使用自动索引值
#define zend_hash_next_index_insert(ht, pData) \
_zend_hash_next_index_insert(ht, pData ZEND_FILE_LINE_CC)
#define zend_hash_next_index_insert_new(ht, pData) \
_zend_hash_next_index_insert_new(ht, pData ZEND_FILE_LINE_CC)
```
#### 7.7.6.3 查找元素
```c
//根据zend_string key查找数组元素
ZEND_API zval* ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key);
//根据普通字符串key查找元素
ZEND_API zval* ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *key, size_t len);
//获取数值索引元素
ZEND_API zval* ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h);
//判断元素是否存在
ZEND_API zend_bool ZEND_FASTCALL zend_hash_exists(const HashTable *ht, zend_string *key);
ZEND_API zend_bool ZEND_FASTCALL zend_hash_str_exists(const HashTable *ht, const char *str, size_t len);
ZEND_API zend_bool ZEND_FASTCALL zend_hash_index_exists(const HashTable *ht, zend_ulong h);
//获取数组元素数
#define zend_hash_num_elements(ht) \
(ht)->nNumOfElements
//与zend_hash_num_elements()类似,会有一些特殊处理
ZEND_API uint32_t zend_array_count(HashTable *ht);
```
#### 7.7.6.4 删除元素
```c
//删除key
ZEND_API int ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key);
//与zend_hash_del()类似,不同地方是如果元素类型为indirect则同时销毁indirect的值
ZEND_API int ZEND_FASTCALL zend_hash_del_ind(HashTable *ht, zend_string *key);
ZEND_API int ZEND_FASTCALL zend_hash_str_del(HashTable *ht, const char *key, size_t len);
ZEND_API int ZEND_FASTCALL zend_hash_str_del_ind(HashTable *ht, const char *key, size_t len);
ZEND_API int ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h);
ZEND_API void ZEND_FASTCALL zend_hash_del_bucket(HashTable *ht, Bucket *p);
```
#### 7.7.6.5 遍历
数组遍历类似foreach的用法,在扩展中可以通过如下的方式遍历:
```c
zval *val;
ZEND_HASH_FOREACH_VAL(ht, val) {
...
} ZEND_HASH_FOREACH_END();
```
遍历过程中会把数组元素赋值给val,除了上面这个宏还有很多其他用于遍历的宏,这里列几个比较常用的:
```c
//遍历获取所有的数值索引
#define ZEND_HASH_FOREACH_NUM_KEY(ht, _h) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h;
//遍历获取所有的key
#define ZEND_HASH_FOREACH_STR_KEY(ht, _key) \
ZEND_HASH_FOREACH(ht, 0); \
_key = _p->key;
//上面两个的聚合
#define ZEND_HASH_FOREACH_KEY(ht, _h, _key) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_key = _p->key;
//遍历获取数值索引key及value
#define ZEND_HASH_FOREACH_NUM_KEY_VAL(ht, _h, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_val = _z;
//遍历获取key及value
#define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_key = _p->key; \
_val = _z;
#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \
ZEND_HASH_FOREACH(ht, 0); \
_h = _p->h; \
_key = _p->key; \
_val = _z;
```
#### 7.7.6.6 其它操作
```c
//合并两个数组,将source合并到target,overwrite为元素冲突时是否覆盖
#define zend_hash_merge(target, source, pCopyConstructor, overwrite) \
_zend_hash_merge(target, source, pCopyConstructor, overwrite ZEND_FILE_LINE_CC)
//导出数组
ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source);
```
```c
#define zend_hash_sort(ht, compare_func, renumber) \
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber)
```
数组排序,compare_func为typedef int (*compare_func_t)(const void *, const void *),需要自己定义比较函数,参数类型为Bucket*,renumber表示是否更改键值,如果为1则会在排序后重新生成各元素的h。PHP中的sort()、rsort()、ksort()等都是基于这个函数实现的。
#### 7.7.6.7 销毁数组
```c
ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);
```
- 前言
- 第1章 PHP基本架构
- 1.1 PHP简介
- 1.2 PHP7的改进
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本实现
- 1.3.3 FPM的初始化
- 1.3.4 请求处理
- 1.3.5 进程管理
- 1.4 PHP执行的几个阶段
- 第2章 变量
- 2.1 变量的内部实现
- 2.2 数组
- 2.3 静态变量
- 2.4 全局变量
- 2.5 常量
- 第3章 Zend虚拟机
- 3.1 PHP代码的编译
- 3.1.1 词法解析、语法解析
- 3.1.2 抽象语法树编译流程
- 3.2 函数实现
- 3.2.1 内部函数
- 3.2.2 用户函数的实现
- 3.3 Zend引擎执行流程
- 3.3.1 基本结构
- 3.3.2 执行流程
- 3.3.3 函数的执行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向对象实现
- 3.4.1 类
- 3.4.2 对象
- 3.4.3 继承
- 3.4.4 动态属性
- 3.4.5 魔术方法
- 3.4.6 类的自动加载
- 3.5 运行时缓存
- 3.6 Opcache
- 3.6.1 opcode缓存
- 3.6.2 opcode优化
- 3.6.3 JIT
- 第4章 PHP基础语法实现
- 4.1 类型转换
- 4.2 选择结构
- 4.3 循环结构
- 4.4 中断及跳转
- 4.5 include/require
- 4.6 异常处理
- 第5章 内存管理
- 5.1 Zend内存池
- 5.2 垃圾回收
- 第6章 线程安全
- 6.1 什么是线程安全
- 6.2 线程安全资源管理器
- 第7章 扩展开发
- 7.1 概述
- 7.2 扩展的实现原理
- 7.3 扩展的构成及编译
- 7.3.1 扩展的构成
- 7.3.2 编译工具
- 7.3.3 编写扩展的基本步骤
- 7.3.4 config.m4
- 7.4 钩子函数
- 7.5 运行时配置
- 7.5.1 全局变量
- 7.5.2 ini配置
- 7.6 函数
- 7.6.1 内部函数注册
- 7.6.2 函数参数解析
- 7.6.3 引用传参
- 7.6.4 函数返回值
- 7.6.5 函数调用
- 7.7 zval的操作
- 7.7.1 新生成各类型zval
- 7.7.2 获取zval的值及类型
- 7.7.3 类型转换
- 7.7.4 引用计数
- 7.7.5 字符串操作
- 7.7.6 数组操作
- 7.8 常量
- 7.9 面向对象
- 7.9.1 内部类注册
- 7.9.2 定义成员属性
- 7.9.3 定义成员方法
- 7.9.4 定义常量
- 7.9.5 类的实例化
- 7.10 资源类型
- 7.11 经典扩展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空间
- 8.1 概述
- 8.2 命名空间的定义
- 8.2.1 定义语法
- 8.2.2 内部实现
- 8.3 命名空间的使用
- 8.3.1 基本用法
- 8.3.2 use导入
- 8.3.3 动态用法
- 附录
- break/continue按标签中断语法实现
- defer推迟函数调用语法的实现
- 一起线上事故引发的对PHP超时控制的思考