本文轉載自: http://segmentfault.com/blog/tree/1190000000718519
HashTable對PHP來說是一種非常重要的資料結構。很多PHP的內部實現(變數的範圍,函數表,類的屬性、方法,數組)就是通過HashTable來實現的。最近瞭解了一下PHP底層HashTable的實現。
PHP底層HashTable的實現有兩個非常重要的結構分別是:HashTable和Bucket。
先說一下HashTable結構:
HashTable的底層實現代碼如下:
typedef struct _hashtable{
uint nTableSize; // hash Bucket的大小,最小為8
uint nTableMask; //nTableSize - 1, 索引取值的最佳化
uint nNumofElements // bucket 裡面存的總數
ulong nNextFreeElement //下一個數字索引的位置
Bucket *pInternalPointer //當前遍曆的指標(foreach比較快的原因)
Bucket *pListHead //整個hashtable的頭指標
Bucket *pListTail //整個hashTable的尾指標
Bucket **argBuckets // Buceket 數組,用來儲存資料
doctor_func_t pDestructor //刪除元素時的回呼函數,用於資源的釋放
zend_bool persistent //Bucket的記憶體配置方式,true使用系統的分配函數,false 使用php的記憶體配置函數
unsigned char nApplyCount //標記當前hash bucket 被遞迴的次數
zend_bool bApplyProtection
#if ZEND_DEBUG
int inconsistent
#endif
}HashTable
建議不太瞭解hash資料結構的同學先簡單瞭解一下hash結構。
簡單說一下php中hashtable的初始化操作:
代碼如下:
ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
uint i = 3;
//...
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = 1 << i;
}
// ...
ht->nTableMask = ht->nTableSize - 1;
/* Uses ecalloc() so that Bucket* == NULL */
if (persistent) {
tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));
if (!tmp) {
return FAILURE;
}
ht->arBuckets = tmp;
} else {
tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));
if (tmp) {
ht->arBuckets = tmp;
}
}
return SUCCESS;
}
最開始判斷需要初始化的hashtable大小是不是超過了系統能使用的最大大小。下面是對tablesize大小的一個處理。將使用者自訂的大小改成需要的大小。例如:如果使用者定義的hashtable大小是6,那初始化時,就會將6變成8,如果使用者定義的大小為11,那初始化後的Hashtable的大小為16.
下面就是一個簡單的判斷,來決定是按照C語言本身的分配記憶體函數來分配記憶體,還是根據php封裝好的記憶體配置函數來分配記憶體。
再談一下 bucket的結構
typedef struct bucket{
ulong h; //對key索引以後的值,數字key不做kash
uint nKeyLength; //key的長度
void *pData;
void *pDataPtr; //指標資料,指向真實資料
struct bucket * pListNext; //整個hash表的下個元素
struct bucket *pListLast; //整個hash表的上個元素
struct bucket *pNext; //本bucket裡面,下一個元素
struct bucket *pLast; //本bucket裡面的上一個元素
char arKey[1];
}Bucket
這裡用一張網路上的很火的圖來說明(圖原地址沒找到,沒有做來源說明):
下面是引用了tipi裡面的插入說明:
引用地址:tipi
如圖中左下角的假設,假設依次插入了Bucket1,Bucket2,Bucket3三個元素:
1、插入Bucket1時,雜湊表為空白,經過雜湊後定位到索引為1的槽位。此時的1槽位只有一個元素Bucket1。 其中Bucket1的pData或者pDataPtr指向的是Bucket1所儲存的資料。此時由於沒有連結關係。pNext, pLast,pListNext,pListLast指標均為空白。同時在HashTable結構體中也儲存了整個雜湊表的第一個元素指標, 和最後一個元素指標,此時HashTable的pListHead和pListTail指標均指向Bucket1。
2、插入Bucket2時,由於Bucket2的key和Bucket1的key出現衝突,此時將Bucket2放在雙鏈表的前面。 由於Bucket2後插入共置於鏈表的前端,此時Bucket2.pNext指向Bucket1,由於Bucket2後插入。 Bucket1.pListNext指向Bucket2,這時Bucket2就是雜湊表的最後一個元素,這是HashTable.pListTail指向Bucket2。\3、插入Bucket3,該key沒有雜湊到槽位1,這時Bucket2.pListNext指向Bucket3,因為Bucket3後插入。 同時HashTable.pListTail改為指向Bucket3。
簡單來說就是雜湊表的Bucket結構維護了雜湊表中插入元素的先後順序,雜湊表結構維護了整個雜湊表的頭和尾。 在操作雜湊表的過程中始終保持預算之間的關係。