前言
本文采用的php版本是7.4.3
另外在了解数组实现前,需要知道zval是什么,如果不知道zval是什么,可以来我的这篇文章看下
https://blog.csdn.net/onlymayao/article/details/104783731
zend_array是什么
php的array类型就是对应的zend_array
zend_array的结构
typedef struct _zend_array zend_array; //首先这是一个结构体
typedef struct _zend_array HashTable; //还定义了一个别名hashtable
//下面来看这个里面定义的什么
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar _unused,
zend_uchar nIteratorsCount,
zend_uchar _unused2)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
zend_array块解析
gc:垃圾回收机制
nTableMask:用来计算索引值
arData:这个里面存储的是key、value对
nNumUsed:表示我已经用了的空间,包含跳过去的,比如我一个数组长度是8,我跳过下标0,直接给下标1赋值,此时的nNumUsed是2.
nNumOfElements:表示真正的元素的个数,结合上个字段的例子,这个值是1
nTableSize:代表arData的大小
nNextFreeElement:这个值主要用在当我们往数组中push数据而不指定下标的时候,比如说$arr[]=1,这个下标值就会从nNextFreeElement拿
需要了解的其他点
hashtable分为packed array 和hash array;
关于packed array:
- 对于key是数字的,就不用涉及到hash运算,此时使用的是packed array
- 当然如果key的值较大,或者间隔较大,还是会退化成hash array。
- packed array 能够节省索引部分占用的内存,是一个性能上的优化;
- 此时索引数组只用了-1和-2
关于hash array:
- 对于key是非数字的,必须用hash算法进行计算出来它所在bucket的位置,那么索引数组是必不可少的,只能是hash array。
/*
* HashTable Data Layout
* =====================
*
* +=============================+
* | HT_HASH(ht, ht->nTableMask) |
* | ... |
* | HT_HASH(ht, -1) |
* +-----------------------------+
* ht->arData ---> | Bucket[0] |
* | ... |
* | Bucket[ht->nTableSize-1] |
* +=============================+
*/
涉及到的面试知识点
问题1:对于一个数组,他初始的大小是多少
答:初始时候的nTableSize是8
问题2:当我们的数组不够用的时候怎么办
答:会进行扩容
问题3:数组每次扩容的大小是多少
答:会在原来的基础上乘以2,比如说8变成16、16变成32,以此类推
问题4:如何解决hash冲突
答:链地址法;主要用zval块中u2.next这个字段来解决hash冲突,前面讲zval的时候咱们提过,不知道大家是否还有印象。
问题5:如何知道bucket的值存放在索引数组的哪个下标
答:对于一个nTableSize为8的数组,此时它的nTableMask是-8,如果我们知道他的hashvalue值是0,那么hashvalue | nTableMask 也就是 0 | -8 得到 -8 就是存放bucket的位置
问题6:解决hash冲突是头插法还是尾插法
答:头插法
问题7:数组除了用在$arr=[],还用在了哪些地方
答:生命周期、虚拟机、内核