在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 */}
Quick Population and Recall當需要對同一個字串的key進行許多操作的時候比如先檢測有沒有,然後插入再修改之類的,可以使用zend_get_hash_value來進行提速。這個函數的返回值可以和quick系列的函數使用,從而達到加速的目的。因為不需要再重複計算字串的散列值,而是直接使用已有的
散列值。
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裡面。}
注意並沒有zend_hash_del函數。
Copy and Merging有三個方法可以進行資料的拷貝,先來看第一個:
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中索引位置相同的元素會被替換掉,而其他的元素則會被保留。
tmp這裡放NULL,低版本才會用到。
size的話代表每個元素的大小,一般是sizeof(zval *)。
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);}
http://www.bkjia.com/PHPjc/635039.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/635039.htmlTechArticle在php中,數組的底層實現就是雜湊表,都是以key-value的形式出現的。在php的Zend引擎中,針對不同的雜湊表操作,都有著專門的對雜湊表進行...