目录
- String 的两个宏定义
- 初始化数组 array_init
- 添加数组数据 add_assoc_null
- 获取数组中HashTable
- 根据 key 获取数组的 value
- 创建一个HashTable
- 获取HashTable元素数量
- 获取HashTable元素的最小/最大值
- HashTable遍历操作
- 获取指定HashPosition位置处的值
- HashTable数据更新
- HashTable的销毁
- HashTable 遍历修改操作示例
String 的两个宏定义
在Zend 内核中,针对字符串,定义了两个宏, 这个在后续的字符串处理中经常需要用到,大家留意
----zend_portability.h----
#define ZEND_STRL(str) (str), (sizeof(str)-1)
#define ZEND_STRS(str) (str), (sizeof(str))
初始化数组
int array_init(zval *arg);
在初始化数组之前, 你需要首先为 zval 分配内存。如下示例:
----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
#define SW_MAKE_STD_ZVAL(p) MAKE_STD_ZVAL(p)
#define SW_ALLOC_INIT_ZVAL(p) ALLOC_INIT_ZVAL(p)
#define sw_zval_ptr_dtor(p) zval_ptr_dtor(*p) //zval销毁
#else /* PHP Version 7 */
//栈上分配空间
#define SW_MAKE_STD_ZVAL(p) zval _stack_zval_##p; p = &(_stack_zval_##p)
#define SW_ALLOC_INIT_ZVAL(p) do{p = (zval *)emalloc(sizeof(zval)); bzero(p, sizeof(zval));}while(0)
#define sw_zval_ptr_dtor(p) zval_ptr_dtor(*p) //zval销毁
#endif
----swoole_server.c----
static PHP_METHOD(swoole_http_client, __construct)
{
...
//SW_MAKE_STD_ZVAL分配的zval内存必须在使用后手动释放
zval *headers;
SW_MAKE_STD_ZVAL(headers);
array_init(headers); //初始化为数组
zend_update_property(swoole_http_client_class_entry_ptr, getThis(), ZEND_STRL("headers"), headers TSRMLS_CC);
sw_zval_ptr_dtor(&headers);
...
//SW_ALLOC_INIT_ZVAL 分配的 zval 内存由 php 回收
zval *ports;
SW_ALLOC_INIT_ZVAL(ports);
array_init(ports); //初始化为数组
zend_update_property(swoole_http_client_class_entry_ptr, getThis(), ZEND_STRL("ports"), ports TSRMLS_CC);
...
}
添加数组数据
//按特定的字符串 key 添加数据
int add_assoc_null(zval *arg, char *key);
int add_assoc_bool(zval *arg, char *key, int val);
int add_assoc_long(zval *arg, char *key, long val);
int add_assoc_double(zval *arg, char *key, double val);
int add_assoc_resource(zval *arg, char *key, int val);
int add_assoc_string(zval *arg, char *key, char *val, int dup);
int add_assoc_stringl(zval *arg, char *key, char *val, uint len, int dup);
int add_assoc_zval(zval *arg, char *key, zval *val);
//按数组索引下标 index 添加数据
int add_index_null(zval *arg, ulong idx);
int add_index_bool(zval *arg, ulong idx, int val);
int add_index_long(zval *arg, ulong idx, long val);
int add_index_resource(zval *arg, ulong idx, int val);
int add_index_double(zval *arg, ulong idx, double val);
int add_index_string(zval *arg, ulong idx, char *val, int dup);
int add_index_stringl(zval *arg, ulong idx, char *val, uint len, int dup);
int add_index_zval(zval *arg, ulong index, zval *val);
//在数组尾部 append 数据,相当于 $arr[] = $new_append_value;
int add_next_index_null(zval *arg);
int add_next_index_bool(zval *arg, int val);
int add_next_index_long(zval *arg, long val);
int add_next_index_resource(zval *arg, int val);
int add_next_index_double(zval *arg, double val);
int add_next_index_string(zval *arg, char *val, int dup);
int add_next_index_stringl(zval *arg, char *val, uint len, int dup);
int add_next_index_zval(zval *arg, zval *val);
参数 | 用途 |
arg | 将要被添加数据的数组 |
key / idx | 要添加数据的数组的键值或者索引下标 |
val | 要添加的特定数据类型的值,这些值将被放到数组HashTable中,注意,这些函数不会使得原始zval的引用计数自动增加 |
len | add_*_stringl 函数需要用到,用于指定要添加的字符串长度 |
dup | 对于字符串类型,该标志接受1或0来指明字符串内容是否被拷贝 |
示例:
----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
#define sw_add_assoc_string add_assoc_string
#define sw_add_assoc_stringl add_assoc_stringl
#define sw_add_assoc_stringl_ex add_assoc_stringl_ex
#define sw_add_assoc_double_ex add_assoc_double_ex
#define sw_add_assoc_long_ex add_assoc_long_ex
#define sw_add_next_index_stringl add_next_index_stringl
#else /* PHP Version 7 */
#define sw_add_assoc_string(array, key, value, duplicate) add_assoc_string(array, key, value)
#define sw_add_assoc_stringl(__arg, __key, __str, __length, __duplicate) add_assoc_stringl_ex(__arg, __key, strlen(__key), __str, __length)
static sw_inline int sw_add_assoc_stringl_ex(zval *arg, const char *key, size_t key_len, char *str, size_t length, int __duplicate)
{
return add_assoc_stringl_ex(arg, key, key_len - 1, str, length);
}
static sw_inline int sw_add_assoc_double_ex(zval *arg, const char *key, size_t key_len, double value)
{
return add_assoc_double_ex(arg, key, key_len - 1, value);
}
static sw_inline int sw_add_assoc_long_ex(zval *arg, const char *key, size_t key_len, long value)
{
return add_assoc_long_ex(arg, key, key_len - 1, value);
}
#define sw_add_next_index_stringl(arr, str, len, dup) add_next_index_stringl(arr, str, len)
#endif
----test.c----
double swoole_microtime(void)
{
struct timeval t;
gettimeofday(&t, NULL);
return (double) t.tv_sec + ((double) t.tv_usec / 1000000);
}
static int test()
{
zval *row_array = NULL;
SW_ALLOC_INIT_ZVAL(row_array);
array_init(row_array);
add_assoc_null(row_array, client->response.columns[i].name);
add_assoc_bool(row_array, "open_http_protocol", 1);
add_assoc_long(row_array, "size", 0);
struct in_addr sin_addr;
sin_addr.s_addr = fd;
sw_add_assoc_string(row_array, "remote_ip", inet_ntoa(sin_addr), 1);
sw_add_assoc_stringl(row_array, "request_uri", ctx->request.path, ctx->request.path_len, 1);
sw_add_assoc_stringl_ex(row_array, ZEND_STRS("path"), path, path_len, 1);
double now_float = swoole_microtime();
sw_add_assoc_double_ex(row_array, ZEND_STRS("request_time_float"), now_float);
sw_add_next_index_stringl(row_array, cookie, strlen(cookie), 0);
}
获取数组中HashTable
通过宏 Z_ARRVAL_P 来获取数组的 HashTable 数据结构:
static int test()
{
zset* zval;
HashTable *vht;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zset) == FAILURE)
{
return;
}
vht = Z_ARRVAL_P(zset);
}
根据 key 获取数组的 value
//按字符串键值查找
int zend_hash_find(HashTable *ht, char *arKey, uint nKeyLength, void **pData);
//按数字索引查找
int zend_hash_index_find(HashTable *ht, ulong index, void **pData);
int zend_hash_quick_find(HashTable *ht, char *arKey, uint nKeyLength,ulong hash_value, void **pData);
参数 | 用途 |
ht | 从数组中获取的 HashTable |
arKey | 要查找的字符串键值名 |
nKeyLength | 不包含NULL结束符的键值名的长度 |
hash_value | 通过 zend_get_hash_value 函数预先计算的哈希值,用于快速查询 zend_hash_quick_find 函数中 |
index | 按数字索引查找的索引下标 |
在下面,我将 zend_hash_find 封装为 PHP5,和 PHP7 下的两个函数。
----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
static inline int sw_zend_hash_find(HashTable *ht, char *k, int len, void **v)
{
zval **tmp = NULL;
if (zend_hash_find(ht, k, len, (void **) &tmp) == SUCCESS)
{
*v = *tmp;
return SUCCESS;
}
else
{
*v = NULL;
return FAILURE;
}
}
#else /* PHP Version 7 */
static inline int sw_zend_hash_find(HashTable *ht, char *k, int len, void **v)
{
zval *value = zend_hash_str_find(ht, k, len);
if (value == NULL)
{
return FAILURE;
}
else
{
*v = (void *) value;
return SUCCESS;
}
}
#endif /* PHP Version */
#define php_swoole_array_get_value(ht, str, v) (sw_zend_hash_find(ht, str, strlen(str), (void **) &v) == SUCCESS && !ZVAL_IS_NULL(v)) //根据 key 获取数组 value
----test.c----
PHP_METHOD(get_array_value_by_key)
{
zval *zset;
HashTable *vht;
zval *v;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zset) == FAILURE)
{
return;
}
vht = Z_ARRVAL_P(zset);
//process_num
if (php_swoole_array_get_value(vht, "process_num", v))
{
php_var_dump(v);
}
RETURN_TRUE;
}
创建一个HashTable
创建一个原始HashTable。 在可能的情况下,应优先使用array_init()和zval_ptr_dtor()方法来完成这些任务,在初始化之前,ht 需要首先通过emalloc、pemalloc或更常见的ALLOC_HASHTABLE(ht)动态分配。 ALLOC_HASHTABLE 宏使用来自特殊池的预先大小的内存块来加速所需的分配时间,并且通常优先于ht = emalloc(sizeof(HashTable))
int zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction,
dtor_func_t pDestructor, zend_bool persistent);
int zend_hash_init_ex(HashTable *ht, uint nSize, hash_func_t pHashFunction,
dtor_func_t pDestructor, zend_bool persistent, zend_bool bApplyProtection);
参数 | 用途 |
ht | 将被创建的 HashTable 指针 |
nSize | 预期持有的元素数量,数量越多将需要更多的内存,但是如果它太小的话,内存不够用时大小会扩充为现在的2倍,但是重建索引是一个开销很大的操作。 |
pHashFunction | 指定散列函数,已过时。 较旧版本的Zend引擎允许重写散列函数。 目前的版本强制DJBX33A。 |
pDestructor | 每当从HashTable中删除元素或替换元素时,都会自动调用该函数。 |
persistent | 是否分配持久化内存 |
bApplyProtection | 设置为非零值时,迭代遍历HashTable的尝试将被限制为最大递归次数。 |
获取HashTable元素数量
//返回 HashTable 元素数量
int zend_hash_num_elements(HashTable *ht);
//返回HashTable中下一个可分配的索引号
ulong zend_hash_next_free_element(HashTable *ht);
参数 | 用途 |
ht | 需要查询的HashTable |
获取HashTable元素的最小/最大值
以下函数用于查找HashTable中的最小/最大值,并放入pData之战返回,并返回他在数组中的索引下标
int zend_hash_minmax(HashTable *ht, compare_func_t compare_func, int findmax, void **pData TSRMLS_DC);
参数 | 用途 |
ht | 需要查询的HashTable |
comapre_func | 用于确定最大/最小值的比较函数 |
findmax | 非0 - 查找最大值 ; 0 - 查找最小值 |
pData | 最小/最大值数据将置于pData中返回 |
HashTable遍历操作
//移动指针到HashTable头部
void zend_hash_internal_pointer_reset_ex(HashTable *ht, HashPosition *pos);
//遍历指针往前移
int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos);
//遍历指针往后移
int zend_hash_move_backwards_ex(HashTable *ht, HashPosition *pos);
//移动指针到HashTable尾部
void zend_hash_internal_pointer_end_ex(HashTable *ht, HashPosition *pos);
//HashTable中是否还有下一个元素
int zend_hash_has_more_elements(HashTable *ht);
参数 | 用途 |
ht | 要移动的HashTable |
pos | 返回遍历位置值。 该值将由zend_hash_internal_pointer_reset_ex()自动初始化,不需要销毁 |
获取指定HashPosition位置处的值
//返回pos指示的HashTable位置处的键类型,包含 HASH_KEY_IS_LONG,HASH_KEY_IS_STRING或HASH_KEY_NON_EXISTANT。
int zend_hash_get_current_key_type_ex(HashTable *ht, HashPosition *pos);
//获取pos指示的HashTable位置处的键值
int zend_hash_get_current_key_ex(HashTable *ht, char **str_index, uint *str_length, ulong *num_index, zend_bool duplicate, HashPosition *pos);
//获取pos指示的HashTable位置处的数据
int zend_hash_get_current_data_ex(HashTable *ht, void **pData, HashPosition *pos);
参数 | 用途 |
ht | 要查询的HashTable指针 |
pos | 指向当前 HashTable 的位置处 |
str_index | 用于返回pos指示的HashTable 位置处的键值 (字符串类型) |
str_length | 包含结束符NULL 的 str_index 的长度 |
num_index | 用于返回pos指示的HashTable 位置处的键值 (数字索引类型数组) |
duplicate | 是否需要拷贝复制键值名(key name) |
pData | 用于返回pos指示的HashTable 位置处的数据 |
HashTable数据更新
int zend_hash_add(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest);
int zend_hash_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest);
int zend_hash_quick_add(HashTable *ht, char *arKey, uint nKeyLength, ulong hash_value, void *pData, uint nDataSize, void **pDest);
int zend_hash_quick_update(HashTable *ht, char *arKey, uint nKeyLength, ulong hash_value, void *pData, uint nDataSize, void **pDest);
int zend_hash_index_update(HashTable *ht, ulong index, void *pData, uint nDataSize, void **pDest);
int zend_hash_next_insert(HashTable *ht, void *pData, uint nDataSize, void **pDest);
参数 | 用途 |
ht | 将被修改的HashTable |
arKey | 以NULL结尾的键值 |
nKeyLength | 包含结束符NULL的键值arKey的长度 |
index | 数组下标索引 |
hash_value | 在快速查询的时候,预先计算hash值 |
pData | 将要存储的数据的指针 |
nDataSize | 以字节为单位的存储数据的大小 |
pDest | 如果需要,则填充指向pData指向的数据副本所在的指针,该指针位于HashTable中。 允许进行适当的修改。 |
HashTable的销毁
HashTable的销毁包含下面4个函数, 各有不同,zend_hash_clean()只会清空HashTable的内容,而destroy变体将释放所有内部结构并使HashTable无法使用。 优雅的destroy 需要稍长时间; 但是,它使HashTable保持一致状态,保证在销毁期间进行访问和修改。
void zend_hash_clean(HashTable *ht);
void zend_hash_destroy(HashTable *ht);
void zend_hash_graceful_destroy(HashTable *ht);
void zend_hash_graceful_reverse_destroy(HashTable *ht);
HashTable 遍历修改操作示例
注意 PHP 7 和 PHP 5 的 HashTable 处理的不同:
----php7_wrapper.h----
#if PHP_MAJOR_VERSION < 7 /* PHP Version 5*/
//数组遍历
#define SW_HASHTABLE_FOREACH_START(ht, entry)\
zval **tmp = NULL;\
for (zend_hash_internal_pointer_reset(ht);\
zend_hash_has_more_elements(ht) == SUCCESS; \
zend_hash_move_forward(ht)) {\
if (zend_hash_get_current_data(ht, (void**)&tmp) == FAILURE) {\
continue;\
}\
entry = *tmp;
//字符串key遍历
#define SW_HASHTABLE_FOREACH_START2(ht, k, klen, ktype, entry)\
zval **tmp = NULL; ulong_t idx;\
for (zend_hash_internal_pointer_reset(ht); \
(ktype = zend_hash_get_current_key_ex(ht, &k, &klen, &idx, 0, NULL)) != HASH_KEY_NON_EXISTENT; \
zend_hash_move_forward(ht)\
) { \
if (zend_hash_get_current_data(ht, (void**)&tmp) == FAILURE) {\
continue;\
}\
entry = *tmp;\
klen --;
#define SW_HASHTABLE_FOREACH_END() }
#define sw_zend_hash_get_current_key(a,b,c,d) zend_hash_get_current_key_ex(a,b,c,d,0,NULL)
#define sw_zend_hash_del zend_hash_del
#define sw_zend_hash_update zend_hash_update
#define sw_zend_hash_index_find zend_hash_index_find
#define sw_zend_hash_add zend_hash_add
#define sw_zend_hash_index_update zend_hash_index_update
#else /* PHP Version 7*/
#define SW_HASHTABLE_FOREACH_START(ht, _val) ZEND_HASH_FOREACH_VAL(ht, _val); {
#define SW_HASHTABLE_FOREACH_START2(ht, k, klen, ktype, _val) zend_string *_foreach_key;\
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _foreach_key, _val);\
if (!_foreach_key) {k = NULL; klen = 0; ktype = 0;}\
else {k = _foreach_key->val, klen=_foreach_key->len; ktype = 1;} {
#define SW_HASHTABLE_FOREACH_END() } ZEND_HASH_FOREACH_END();
static inline int sw_zend_hash_del(HashTable *ht, char *k, int len) {
return zend_hash_str_del(ht, k, len - 1);
}
static inline int sw_zend_hash_update(HashTable *ht, char *k, int len, zval *val, int size, void *ptr) {
return zend_hash_str_update(ht, (const char*)k, len -1, val) ? SUCCESS : FAILURE;
}
static inline int sw_zend_hash_add(HashTable *ht, char *k, int len, void *pData, int datasize, void **pDest) {
return zend_hash_str_add(ht, k, len - 1, pData) ? SUCCESS : FAILURE;
}
static inline int sw_zend_hash_index_update(HashTable *ht, int key, void *pData, int datasize, void **pDest) {
return zend_hash_index_update(ht, key, pData) ? SUCCESS : FAILURE;
}
#endif
----swoole_client.c----
//字符串遍历,下面模拟一个 array_keys 函数
HashTable* ycroute_get_array_keys(zval *p) {
if(!ycroute_is_array(p)) {
return NULL;
}
char * key;
zval *value;
uint32_t key_len;
int key_type;
ulong_t num = 0;
HashTable *new_hash;
ALLOC_HASHTABLE(new_hash);
zend_hash_init(new_hash, zend_hash_num_elements(Z_ARRVAL_P(p)), NULL, ZVAL_PTR_DTOR, 0);
YC_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(p), key, key_len, key_type, value)
if (HASH_KEY_IS_STRING != key_type) { //非字符串
continue;
}
zval *zval_key;
YC_ALLOC_INIT_ZVAL(zval_key);
YC_ZVAL_STRING(zval_key, key, 1);
yc_zend_hash_index_update(new_hash, num, (void*) zval_key, sizeof(zval *), NULL);
num++;
YC_HASHTABLE_FOREACH_END();
return new_hash;
}
//数组遍历
static int client_select_wait(zval *sock_array, fd_set *fds TSRMLS_DC)
{
zval *element = NULL;
int sock;
ulong_t num = 0;
#if PHP_MAJOR_VERSION < 7
HashTable *new_hash;
char *key = NULL;
zval **dest_element = NULL;
uint32_t key_len;
ALLOC_HASHTABLE(new_hash);
//初始化 new_hash ,大小为 sock_array 数组的元素个数。
zend_hash_init(new_hash, zend_hash_num_elements(Z_ARRVAL_P(sock_array)), NULL, ZVAL_PTR_DTOR, 0);
SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(sock_array), element)
sock = swoole_convert_to_fd(element TSRMLS_CC);
if ((sock < FD_SETSIZE) && FD_ISSET(sock, fds)) {
switch (sw_zend_hash_get_current_key(Z_ARRVAL_P(sock_array), &key, &key_len, &num)) { //判断键值类型
case HASH_KEY_IS_STRING:
sw_zend_hash_add(new_hash, key, key_len, (void * ) &element, sizeof(zval *), (void ** )&dest_element);
break;
case HASH_KEY_IS_LONG:
sw_zend_hash_index_update(new_hash, num, (void * ) &element, sizeof(zval *), (void ** )&dest_element);
break;
}
if (dest_element) {
sw_zval_add_ref(dest_element);
}
}
num ++;
SW_HASHTABLE_FOREACH_END();
zend_hash_destroy(Z_ARRVAL_P(sock_array));
efree(Z_ARRVAL_P(sock_array));
zend_hash_internal_pointer_reset(new_hash);
Z_ARRVAL_P(sock_array) = new_hash;
#else
zval new_array;
array_init(&new_array);
zend_ulong num_key;
zend_string *key;
zval *dest_element;
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(sock_array), num_key, key, element) {
sock = swoole_convert_to_fd(element TSRMLS_CC);
if ((sock < FD_SETSIZE) && FD_ISSET(sock, fds)) {
if (key) {
dest_element = zend_hash_add(Z_ARRVAL(new_array), key, element);
} else {
dest_element = zend_hash_index_update(Z_ARRVAL(new_array), num_key, element);
}
if (dest_element) {
Z_ADDREF_P(dest_element);
}
}
num++;
} ZEND_HASH_FOREACH_END();
zval_ptr_dtor(sock_array);
ZVAL_COPY_VALUE(sock_array, &new_array);
#endif
return num ? 1 : 0;
}
HashTable的拷贝与合并
void zend_hash_copy(HashTable *dst, HashTable *src, copy_ctor_func_t pCopyConstructor,
void *tmp, uint size);
void zend_hash_merge(HashTable *dst, HashTable *src, copy_ctor_func_t pCopyConstructor,
void *tmp, uint size, int overwrite);
void zend_hash_merge_ex(HashTable *dst, HashTable *src, copy_ctor_func_t pCopyConstructor,
uint size, merge_checker_func_t pMergeSource,void *pParam);
zend_hash_copy()函数将src中的每个元素都将被复制到dst。使用pCopyConstructor方法可以保证在需要时执行其他资源复制。 zend_hash_merge()与zend_hash_copy()唯一的不同在于最后的overwrite参数. 当将它设置为非0值时, zend_hash_merge()的行为和zend_hash_copy()一致. 当它设置为0时,跳过已经存在的元素。zend_hash_merge_ex()允许使用一个合并检查函数有选择的拷贝。