一、雜湊表定義
雜湊表(或散列表),是將鍵名key按指定的散列函數HASH經過HASH(key)計算後映射到表中一個記錄,而這個數組就是雜湊表。
這裡的HASH指任意的函數,例如MD5、CRC32、SHA1或你自訂的函數實現。
二、HashTable效能
HashTable是一種尋找效能極高的資料結構,在很多語言內部都實現了HashTable。
理想情況下HashTable的效能是O(1)的,效能消耗主要集中在散列函數HASH(key),通過HASH(key)直接定位到表中的記錄。
而在實際情況下經常會發生key1 != key2,但HASH(key1) = HASH(key2),這種情況即Hash碰撞問題,碰撞的機率越低HashTable的效能越好。當然Hash演算法太過複雜也會影響HashTable效能。
三、理解PHP的雜湊表實現
在PHP核心也同樣實現了HashTable並廣泛應用,包括安全執行緒、全域變數、資源管理等基本上所有的地方都能看到它的身影。
不僅如此,在PHP指令碼中數組(PHP的數組實質就是HashTable)也是被廣泛使用的,例如數組形式的設定檔、資料庫的查詢結果等,可以說是無處不在。
那麼既然PHP的數組使用率這麼高,內部是如何?的?它如何解決hash碰撞及實現均勻分布的?PHP指令碼使用數組應該注意哪些?
首先通過圖解,大致理解PHP HashTable的實現。修正:之前認為PHP解決Hahs衝突時,鏈表使用的是單向鏈表。
查看\Zend\zend_hash.c的zend_hash_move_backwards_ex方法與zend_hash_del_key_or_index方法後,實際上使用的是雙向鏈表
下面通過源碼來一步一步分析。
1)HashTable在PHP核心的實現
PHP實現HashTable主要是通過兩個資料結構Bucket(桶)和HashTable。
從PHP指令碼端來看,HashTable相當於Array對象,而Bucket相當於Array對象裡的某個元素。對於多維陣列實際就是HashTable的某個Bucket裡儲存著另一個HashTable。
HashTable結構:
typedef struct _hashtable { uint nTableSize; //表長度,並非元素個數 uint nTableMask;//表的掩碼,始終等於nTableSize-1 uint nNumOfElements;//儲存的元素個數 ulong nNextFreeElement;//指向下一個空的元素位置 Bucket *pInternalPointer;//foreach迴圈時,用來記錄當前遍曆到的元素位置 Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets;//儲存的元素數組 dtor_func_t pDestructor;//解構函式 zend_bool persistent;//是否持久儲存。從這可以發現,PHP數組是可以實現持久儲存在記憶體中的,而無需每次請求都重新載入。 unsigned char nApplyCount; zend_bool bApplyProtection;} HashTable;
Bucket結構:
typedef struct bucket { ulong h; //數組索引 uint nKeyLength; //字串索引的長度 void *pData; //實際資料的儲存地址 void *pDataPtr; //引入的資料存放區地址 struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; //雙向鏈表的下一個元素的地址 struct bucket *pLast;//雙向鏈表的下一個元素地址 char arKey[1]; /* Must be last element */} Bucket;
PHP核心雜湊表的散列函數很簡單,直接使用 (HashTable->nTableSize & HashTable->nTableMask)的結果作為散列函數的實現。這樣做的目的可能也是為了降低Hash演算法的複雜度和提高效能。
1.1)在PHP中初始化一個空數組時,對應核心中是如何建立HashTable的
$array = new Array();
//省略了部分代碼,提出主要的邏輯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; Bucket **tmp; SET_INCONSISTENT(HT_OK); if (nSize >= 0x80000000) {//數組的最大長度是十進位2147483648 /* prevent overflow */ ht->nTableSize = 0x80000000; } else { //數組的長度是向2的整次冪取圓整 //例如數組的裡有10個元素,那麼實際被分配的HashTable長度是16。100個元素,則被分配128的長度 //HashTable的最小長度是8,而非0。因為預設是將1向右移3位,1<<3=8 while ((1U << i) < nSize) { i++; } ht->nTableSize = 1 << i; } ht->nTableMask = ht->nTableSize - 1; .... return SUCCESS;}
從上看出,即使在PHP中初始化一個空數組或不足8個元素的數組,都會被建立8個長度的HashTable。同樣建立100個元素的數組,也會被分配128長度的HashTable。依次類推。
1.2)核心對PHP添加數字索引的處理方式PHP數組中,鍵名可以為數字或字串類型。而在核心中只允許數字索引,對於字串索引,核心採用了time33演算法將字串轉換為整型。具體的實現下面會詳細說明。
$array[0] = "hello hashtable";
//省略了部分代碼,提出主要的邏輯ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC){ ulong h; uint nIndex; Bucket *p; //省略了部分代碼,提出主要的邏輯 nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; p = (Bucket *) pemalloc_rel(sizeof(Bucket) - 1, ht->persistent); if (!p) { return FAILURE; } p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */ p->h = h; INIT_DATA(ht, p, pData, nDataSize); if (pDest) { *pDest = p->pData; } ht->arBuckets[nIndex] = p; ht->nNumOfElements++; return SUCCESS;}
上述也說明了,核心中雜湊表的散列函數就是簡單的h & ht->nTableMask,其中h代表PHP中設定的索引號,nTableMask等於雜湊表分配的長度-1。
1.3) 核心對PHP中字串索引的處理方式
$array['index'] = "hello hashtable";
與數字索引相比,只是多了一步將字串轉換為整型。用到的演算法是time33
下面貼出了演算法的實現,就是對字串的每個字元轉換為ASCII碼乘上33並且相加得到的結果。
static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength){ register ulong hash = 5381; /* variant with the hash unrolled eight times */ for (; nKeyLength >= 8; nKeyLength -= 8) { hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; } switch (nKeyLength) { case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 1: hash = ((hash << 5) + hash) + *arKey++; break; case 0: break; } return hash;}zend_hash.c//下面省略了部分代碼,提出主要的邏輯ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC){ ulong h; uint nIndex; Bucket *p; h = zend_inline_hash_func(arKey, nKeyLength); //字串轉整型 nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; p = (Bucket *) pemalloc_rel(sizeof(Bucket) - 1, ht->persistent); if (!p) { return FAILURE; } p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */ p->h = h; INIT_DATA(ht, p, pData, nDataSize); if (pDest) { *pDest = p->pData; } ht->arBuckets[nIndex] = p; ht->nNumOfElements++; return SUCCESS;}
2) 核心中如何?均勻分布和解決hash碰撞問題的
2.1) 均勻分布
均勻分布是指,將需要儲存的各個元素均勻的分布到HashTable中。
而負責計算具體分布到表中哪個位置的函數就是散列函數做的事情,所以散列函數的實現直接關係到均勻分布的效率。
上面也提到了PHP核心中用了簡單的方式實現:h & ht->nTableMask;
2.1)Hash碰撞
Hash碰撞是指,經過Hash演算法後得到的值會出現key1 != key2, 但Hash(key1)卻等於Hash(key2)的情況,這就是碰撞問題。
在PHP核心來看,就是會出現key1 != key2, 但key1 & ht->nTableMask卻等於 key2 & ht->nTableMask的情況。
PHP核心使用雙向鏈表的方式來儲存衝突的資料。即Bucket本身也是一個雙向鏈表,當發生衝突時,會將資料按順序向後排列。
如果不發生衝突,Bucket即是長度為1的的雙向鏈表。
ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData){ ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; //找到元素時,並非立即返回,而是要再對比h與nKeyLength,防止hash碰撞。此段代碼就是遍曆鏈表,直到鏈表尾部。 while (p != NULL) { if ((p->h == h) && (p->nKeyLength == nKeyLength)) { if (!memcmp(p->arKey, arKey, nKeyLength)) { *pData = p->pData; return SUCCESS; } } p = p->pNext; } return FAILURE;}
之後,將會寫一篇關於利用Hash演算法,進行分布式儲存的介紹。
原文地址:http://blog.csdn.net/a600423444/article/details/8850617