在php中,數組的底層實現就是哈希表,都是以key-value的形式出現的。在php的Zend引擎中,針對不同的哈希表操作,都有著專門的對哈希表進行操作的api。
Creation
對於哈希表而言,每次初始化的方式都是一樣的,都由下面這個函數zend_hash_init來完成:
int zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent)其中ht是指向哈希表的指針,既可以對一個已存在的hashtable變量取引用。也可以為新的hashtable申請內存。一般的方法就是:
ALLOC_HASHTABLE(ht),相當於ht = emalloc(sizeof(HashTable));。
nSize是哈希表的最大元素數,是為了提前申請好內存考慮的。如果它不是2的指數倍,會根據下式增長nSize = pow(2, ceil(log(nSize, 2)));,比如如果給了5,那麼會增長到8.這個應該是為了內存管理比較方便所采用的機制。
pHashFunction屬於以前版本的zend eigine函數,在新版本中一直設為NULL即可。
pDestructor指向當哈希表中的元素被刪掉的時候(zend_hash_del() zend_hash_update())所調用的方法的入口,也就是一個相應的回調函數。假如說給定了method_name函數,那麼在函數實現的時候:
void method_name(void *pElement)
pElement指向被刪掉的元素
persistent這個是一個標志位,表示是否是持久型的哈希表,持久型的數據是獨立於請求之外的,不會在RSHUTDOWN的時候被注銷掉。但是如果設1的話,那麼ht在申請內存的時候一定要使用pemalloc().
舉個例子:在每個php請求生命周期中對symbol_table初始化的時候都會看到zend_hash_init(&EG(symbol_table), 50, NULL, ZVAL_PTR_DTOR, 0);
每當unset的時候,相應的存儲在哈希表中的zval*都被發送給zval_ptr_dtor()進行銷毀。
Population:
有四種主要的插入和更新哈希表中數據的函數:
int zend_hash_add(HashTable *ht, char *arKey, uint nKeyLen, void *pData, uint nDataSize, void **pDest); int zend_hash_update(HashTable *ht, char *arKey, uint nKeyLen, void *pData, uint nDataSize, void **pDest); int zend_hash_index_update(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest); int zend_hash_next_index_insert(HashTable *ht, void *pData, uint nDataSize, void **pDest);前兩個函數添加帶字符串索引的數據到hashtable中,比如php中$foo['bar'] = 'barvalue',那麼在擴展中:
zend_hash_add(fooHashTbl, "bar", sizeof("bar"), &barZval, sizeof(zval*), NULL);
就把相應key值和對應的表值加入到了hashtable中去了。
add和update唯一的區別是如果key已經存在的話,add會失敗的。
後兩個函數是向ht中添加數字索引的數據。
zend_hash_next_index_insert()函數不需要索引值參數,而是自己直接計算出下一個數字索引值。
而如果想自己獲得下一個元素的數字索引值也可以通過zend_hash_next_free_element()來獲得索引。
ulong nextid = zend_hash_next_free_element(ht);
zend_hash_index_update(ht, nextid, &data, sizeof(data), NULL);
上面這段代碼就相當於:
zend_hash_next_index_insert(HashTable *ht, &data,sizeof(data),NULL).
其中pDest參數可以用來存儲新加入的元素的地址值。
Recall:查找
一般來說,有兩種獲得哈希表中數據的方法:
int zend_hash_find(HashTable *ht, char *arKey, uint nKeyLength, void **pData); int zend_hash_index_find(HashTable *ht, ulong h, void **pData);
void hash_sample(HashTable *ht, sample_data *data1) { sample_data *data2; ulong targetID = zend_hash_next_free_element(ht);//獲取下一個索引的位置 if (zend_hash_index_update(ht, targetID, data1, sizeof(sample_data), NULL) == FAILURE) {//把數據data1插入到哈希表的下一個索引的位置中去 /* Should never happen */ return; } if(zend_hash_index_find(ht, targetID, (void **)&data2) == FAILURE) {//利用id去尋找哈希表中的值,如果找到的話把值放在data2中。 /* Very unlikely since we just added this element */ return; } /* data1 != data2, however *data1 == *data2 */ }除了獲得哈希表中的值之外,有的時候更重要的是知道一些元素的存在:
int zend_hash_exists(HashTable *ht, char *arKey, uint nKeyLen); int zend_hash_index_exists(HashTable *ht, ulong h);分別針對字符串索引和數字的索引。返回的是1和0.
if (zend_hash_exists(EG(active_symbol_table), "foo", sizeof("foo"))) {//確定活動的符號表中是否存在foo變量 /* $foo is set */ } else { /* $foo does not exist */ }
ulong zend_get_hash_value(char *arKey, uint nKeyLen);用這個返回值傳給下面的quick系列函數就可以達到加速的目的:
int zend_hash_quick_add(HashTable *ht, char *arKey, uint nKeyLen, ulong hashval, void *pData, uint nDataSize, void **pDest); int zend_hash_quick_update(HashTable *ht, char *arKey, uint nKeyLen, ulong hashval, void *pData, uint nDataSize, void **pDest); int zend_hash_quick_find(HashTable *ht, char *arKey, uint nKeyLen, ulong hashval, void **pData); int zend_hash_quick_exists(HashTable *ht, char *arKey, uint nKeyLen, ulong hashval);
void php_sample_hash_copy(HashTable *hta, HashTable *htb, char *arKey, uint nKeyLen TSRMLS_DC) { ulong hashval = zend_get_hash_value(arKey, nKeyLen);//獲得用來加速的散列值hashval zval **copyval; if (zend_hash_quick_find(hta, arKey, nKeyLen, hashval, (void**)©val) == FAILURE) {//首先要在hta table裡面找到相應的元素,並且存儲在copyval中。 /* arKey doesn't actually exist */ return; } /* The zval* is about to be owned by another hash table */ (*copyval)->refcount__gc++;//相應zval*變量的引用次數+1 zend_hash_quick_update(htb, arKey, nKeyLen, hashval, copyval, sizeof(zval*), NULL);//把從hta中拿來的copyval放在htb裡面。 }
typedef void (*copy_ctor_func_t)(void *pElement); void zend_hash_copy(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size);在source中的每個元素都會被拷貝到target中.通過pCopyConstructor的處理可以使得在拷貝變量的時候對這些變量的ref_count進行加一的操作。target中原有的與source中索引位置相同的元素會被替換掉,而其他的元素則會被保留。
void zend_hash_merge(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, void *tmp, uint size, int overwrite);主要是多了一個overwrite的參數,如果非0,那就跟copy一樣,如果是0,那就對於已經存在的元素就不會進行復制了。
typedef zend_bool (*merge_checker_func_t)(HashTable *target_ht, void *source_data, zend_hash_key *hash_key, void *pParam); void zend_hash_merge_ex(HashTable *target, HashTable *source, copy_ctor_func_t pCopyConstructor, uint size, merge_checker_func_t pMergeSource, void *pParam);pMergeSource回調函數使得可以選擇性的進行合並,而不是全部合並,這個給人的感覺有點像c語言裡面快速排序函數所留的函數入口,可以決定排序的方式。
zend_bool associative_only(HashTable *ht, void *pData, zend_hash_key *hash_key, void *pParam) { /* True if there's a key, false if there's not */ return (hash_key->arKey && hash_key->nKeyLength);//字符串類型的key,因為存在nKeyLength } void merge_associative(HashTable *target, HashTable *source) { zend_hash_merge_ex(target, source, zval_add_ref, sizeof(zval*), associative_only, NULL); }