PHP HashTable介紹總結
本篇文章主要是對 PHP HashTable 總結,下面的參考鏈接是很好的學習資料。學習“散列”這個數據結構—推薦《數據結構與算法分析 C語言描述》
總結
HashTable 又叫做散列表,是一種用於以常數平均時間執行插入、刪除和查找的技術。不能有效的支持元素之間的排序。——《數據結構與算法分析 C語言描述》
HashTable 是 PHP 的靈魂,因為在 Zend 引擎中大量的使用了 HashTable,如變量表,常量表,函數表等,這些都是使用 HashTable 保存的,另外,PHP 的數組也是通過使用 HashTble 實現的。
接下來看下面這一句話:
Hashtable是非常常見的數據結構,它被設計出來解決計算機只能直接表示以連續的整數作為索引的數組的問題。使用Hashtable,程序員才能使用字符串或者其他的復合類型作為數組的鍵。
Hashtable 的概念:字符串的鍵先會被傳遞給一個 hash 函數(hashing function,中文也翻譯為散列函數),然後這個函數會返回一個整數,而這個整數就是“通常”的數組的索引,通過這個數字索引可以訪問到 字符串的鍵對應的數據。
關於 HashTable 的幾個概念
- 鍵(key):用於操作數據的標示,例如PHP數組中的索引,或者字符串鍵等等。
- 槽(slot/bucket):哈希表中用於保存數據的一個單元,也就是數據真正存放的容器。
- 哈希函數(hash function):將key映射(map)到數據應該存放的slot所在位置的函數。
- 哈希沖突(hash collision):哈希函數將兩個不同的key映射到同一個索引的情況。
對比 PHP 的數組和 C 語言的數組,發現 PHP 的數組確實支持更多的寫法,下標不僅可以是數字也可以是字母等。另一方面 HashTable 是無序的,那 PHP 數組的順序結構是怎麽實現的呢?可以帶著這些疑問閱讀 PHP 源碼。
源碼版本: php-7.1.19-src
解決哈希沖突一般有兩種方式,PHP 使用的是分離鏈接法,既當產生沖突時,數據形成一個鏈表。
Hashtable 的數據結構
1 typedef struct _Bucket { 2 zval val; /* 存儲的具體數據 */ 3 zend_ulong h; /* 一個 hash 值 */ 4 zend_string *key; /* 一個字符串鍵 */ 5 } Bucket; 6 7 typedef struct _zend_array HashTable; 8 9 struct _zend_array { 10 zend_refcounted_h gc; 11 union { 12 struct { 13 ZEND_ENDIAN_LOHI_4( 14 zend_uchar flags, 15 zend_uchar nApplyCount, 16 zend_uchar nIteratorsCount, 17 zend_uchar consistency) 18 } v; 19 uint32_t flags; 20 } u; 21 uint32_t nTableMask; /* 掩碼,用於根據hash值計算存儲位置,永遠等於nTableSize-1 */ 22 Bucket *arData; /* 存放實際數據 */ 23 uint32_t nNumUsed; /* arData數組已經使用的數量 */ 24 uint32_t nNumOfElements; /* hash表中元素個數 */ 25 uint32_t nTableSize; /* hash表的大小 */ 26 uint32_t nInternalPointer; /* 用於HashTable遍歷 */ 27 zend_long nNextFreeElement; /* 下一個空閑可用位置的數字索引 */ 28 dtor_func_t pDestructor; /* 析構函數 */ 29 };
結構體 _Bucket 代表的是 PHP 裏數組的元素, _zend_array 為 PHP 具體功能添加了一些必要的數據。
例如當將一個元素從哈希表刪除時並不會將對應的Bucket移除,而是將Bucket存儲的zval標示為IS_UNDEF
,所以使用 nNumOfElements 保存 Hash 的元素個數,使用 nNumUsed 表示數組真實的使用數量。
nTableSize 表示分配的內存大小,最小為8。
HashTable中另外一個非常重要的值 arData
,這個值指向存儲元素數組的第一個Bucket,插入元素時按順序依次插入數組,比如第一個元素在arData[0]、第二個在arData[1]...arData[nNumUsed]。PHP數組的有序性正是通過arData
保證的。
哈希表實現的關鍵是有一個數組存儲哈希值與 Bucket 的映射,但是HashTable中並沒有這樣一個索引數組。實際上這個索引數組包含在arData
中,在內存中一塊存在。具體的位置如下圖。
所以,整體來看 HashTable 主要依賴 arData 實現元素的存儲、索引。插入一個元素時先將元素插入Bucket數組,位置是 index,再根據key的哈希值與nTableMask計算出索引數組的位置,將 index 存入這個位置;查找時先根據 key 的哈希值與 nTableMask 計算出索引數組的位置,獲得元素在 Bucket 數組的位置 index,再從 Bucket 數組中取出元素。
哈希表的大小為2^n,插入時如果容量不夠則首先檢查已刪除元素所占比例,如果達到閾值(ht->nNumUsed - ht->nNumOfElements > (ht->nNumOfElements >> 5),則將已刪除元素移除,重建索引,如果未到閾值則進行擴容操作,擴大為當前大小的2倍,將當前Bucket數組復制到新的空間,然後重建索引。
參考
PHP 7中新的Hashtable實現和性能改進
PHP internals Book
PHP 哈希表(數組)的內核實現
PHP HashTable介紹總結