PHP内核之旅-5.强大的数组
PHP 内核之旅系列
一、数组的内部结构
1.底层实现为散列表(HashTable,也称作哈希表)
2.散列表的概念:
是根据关键码值(Key value)而直接进行访问的数据结构。通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。复杂度为O(1)。
文件路径\Zend\zend_types.h
_zend_array结构:
1 typedef struct _zend_array zend_array; 2 typedef struct _zend_array HashTable; 3 4 struct _zend_array { 5 zend_refcounted_h gc; 6 union { 7 struct { 8 ZEND_ENDIAN_LOHI_4( 9 zend_uchar flags, 10 zend_uchar nApplyCount, 11 zend_uchar nIteratorsCount, 12 zend_uchar consistency) 13 } v; 14 uint32_t flags; 15 } u; 16 uint32_t nTableMask; 17 Bucket *arData; 18 uint32_t nNumUsed; 19 uint32_t nNumOfElements; 20 uint32_t nTableSize; 21 uint32_t nInternalPointer; 22 zend_long nNextFreeElement; 23 dtor_func_t pDestructor; 24 };
zend_array和HashTable结构相同
arData:散列表中保存存储元素的数组,其内存是连续的,arData指向数组的起始位置,其内存连续。
nTableSize:数组的总容量,可以容纳的元素数,大小是2的幂次方,最小为8
nTableMask: 映射元素的存储位置用到,nTableSize的负数
nNumUsed: 数组当前使用的Bucket数,删除元素时,不会将其从数组中移除,将这个元素的类型标为IS_UNDEF,当数组容量超限,扩容时才会删除。
nNumOfElements:数组中有效元素的位置
nNextFreeElement:下一个数值的索引
pDestructor:删除或覆盖数组中的某个元素时,则调用此函数对旧元素进行处理
u:辅助作用
Bucket结构:
1 typedef struct _Bucket { 2 zval val; 3 zend_ulong h; /* hash value (or numeric index) */ 4 zend_string *key; /* string key or NULL for numerics */ 5 } Bucket;
val: 具体的value
h: key的hash值,或者数值索引
*key: 存储元素的key,如果元素是数值索引则为NULL
二、数组的基本实现
散列函数:将元素进行hash运算后的值,对数组大小取模之后的值(下标:0~7)分配到中间映射表
中间映射表:元素和下标的映射关系表。可以通过arData向前访问到
元素数组:实际存储元素的数组,按照下标有序存储元素
三、数组的初始化
\Zend\zend_hash.c
1 ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) 2 { 3 GC_REFCOUNT(ht) = 1; 4 GC_TYPE_INFO(ht) = IS_ARRAY | (persistent ? 0 : (GC_COLLECTABLE << GC_FLAGS_SHIFT)); 5 ht->u.flags = (persistent ? HASH_FLAG_PERSISTENT : 0) | HASH_FLAG_APPLY_PROTECTION | HASH_FLAG_STATIC_KEYS; 6 ht->nTableMask = HT_MIN_MASK; 7 HT_SET_DATA_ADDR(ht, &uninitialized_bucket); 8 ht->nNumUsed = 0; 9 ht->nNumOfElements = 0; 10 ht->nInternalPointer = HT_INVALID_IDX; 11 ht->nNextFreeElement = 0; 12 ht->pDestructor = pDestructor; 13 ht->nTableSize = zend_hash_check_size(nSize); 14 }
\Zend\zend_API.c
1 ZEND_API int _array_init(zval *arg, uint32_t size ZEND_FILE_LINE_DC) /* {{{ */ 2 { 3 ZVAL_NEW_ARR(arg); 4 _zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC); 5 return SUCCESS; 6 }
四、插入
插入时首先会检查数组已经分配存储空间,初始化时没有实际分配arData的内存,第一次插入时才会根据nTableSize的大小分配,分配完以后会把HashTable->u.flags打上HASH_FLAG_INITIALIZED掩码,下次插入时发现已经分配了就不会再重复操作。
1 static zend_always_inline void zend_hash_check_init(HashTable *ht, int packed) 2 { 3 HT_ASSERT_RC1(ht); 4 if (UNEXPECTED(!((ht)->u.flags & HASH_FLAG_INITIALIZED))) { 5 zend_hash_real_init_ex(ht, packed); 6 } 7 } 8 9 #define CHECK_INIT(ht, packed) \ 10 zend_hash_check_init(ht, packed)
参考资料:
http://www.php-internals.com/
PHP7内核剖析
作 者:
Jackson0714
出 处:http://www.cnblogs.com/jackson0714/
关于作者:专注于微软平台的项目开发。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者直接私信我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是作者坚持原创和持续写作的最大动力!