background image

作,并且会消耗比 value 大的一块内存(多出的内存用于存放 cookie),如果 value 很小
的话,将会造成较大的浪费。考虑到 HashTable 多用于存放指针值,于是 Zend 引入
pDataPtr,当 value 小到和指针一样长时,Zend 就直接将其复制到 pDataPtr 里,并且将
pData 指向 pDataPtr。这就避免了 emalloc 操作,同时也有利于提高 Cache 命中率。
arKey 大小为什么只有 1?为什么不使用指针管理 key?
arKey 是存放 key 的数组,但其大小却只有 1,并不足以放下 key。在 HashTable 的初始化
函数里可以找到如下代码:

复制代码代码如下:

p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);

可见,Zend 为一个 Bucket 分配了一块足够放下自己和 key 的内存,上半部分是 Bucket,
下半部分是 key,而 arKey“

恰好 是 Bucket 的最后一个元素,于是就可以使用 arKey 来访

问 key 了。这种手法在内存管理例程中最为常见,当分配内存时,实际上是分配了比指定
大小要大的内存,多出的上半部分通常被称为 cookie,它存储了这块内存的信息,比如
块大小、上一块指针、下一块指针等,baidu 的 Transmit 程序就使用了这种方法。
不用指针管理 key,是为了减少一次 emalloc 操作,同时也可以提高 Cache 命中率。另一个
必需的理由是,key 绝大部分情况下是固定不变的,不会因为 key 变长了而导致重新分配
整个 Bucket。这同时也解释了为什么不把 value

——

也一起作为数组分配了

因为 value 是可

变的。

1.2.2 PHP 数组

关于 HashTable 还有一个疑问没有回答,就是 nNextFreeElement 是干什么的?
不同于一般的散列,Zend 的 HashTable 允许用户直接指定 hash 值,而忽略 key,甚至可以
不指定 key(此时,nKeyLength 为 0)。同时,HashTable 也支持 append 操作,用户连 hash
值也不用指定,只需要提供 value,此时,Zend 就用 nNextFreeElement 作为 hash,之后将
nNextFreeElement 递增。
HashTable 的这种行为看起来很奇怪,因为这将无法按 key 访问 value,已经完全不是个散
列了。理解问题的关键在于,PHP 数组就是使用 HashTable

——

实现的

关联数组使用正常

的 k-v 映射将元素加入 HashTable,其 key 为用户指定的字符串;非关联数组则直接使用
数组下标作为 hash 值,不存在 key;而当在一个数组中混合使用关联和非关联时,或者
使用 array_push 操作时,就需要用 nNextFreeElement 了。
再来看 value,PHP 数组的 value 直接使用了 zval 这个通用结构,pData 指向的是 zval*,
按照上一节的介绍,这个 zval*将直接存储在 pDataPtr 里。由于直接使用了 zval,数组的元
素可以是任意 PHP 类型。
数组的遍历操作,即 foreach、each 等,是通过 HashTable 的双向链表来进行的,
pInternalPointer 作为游标记录了当前位置。

1.2.3 变量符号表

除了数组,HashTable 还被用来存储许多其他数据,比如,PHP 函数、变量符号、加载的模