HashTable添加和更新的函數:數組
有4個主要的函數用於插入和更新HashTable的數據:函數
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);
這裏的前兩個函數用於新增關聯索引數據, 好比$foo['bar'] = 'baz';對應的C語言代碼以下:ui
zend_hash_add(fooHashTbl, "bar", sizeof("bar"), &barZval, sizeof(zval*), NULL);
zend_hash_add()和zend_hash_update()惟一的區別是若是key存在, zend_hash_add()將會失敗.google
接下來的兩個函數以相似的方式處理數值索引的HashTable. 這兩行之間的區別在因而否指定索引 或者說是否自動賦值爲下一個可用索引.spa
若是須要存儲使用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);
HashTable添加更新元素:調試
在初始化了HashTable以後,能夠用zend_hash_add來向HashTable添加元素 ,zend_hash_add是一個宏:code
#define zend_hash_add(ht, arKey, nKeyLength, pData, nDataSize, pDest) \ _zend_hash_add_or_update(ht, arKey, nKeyLength, pData, nDataSize, pDest, HASH_ADD ZEND_FILE_LINE_CC)
咱們來看看_zend_hash_add_or_update的定義,一樣在Zend/zend_hash.c下blog
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; /*存貯arKey在hash以後的值*/
uint nIndex; /*存貯h & nTableMask以後的值*/ Bucket *p; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); //這個還不知道是什麼意思
#endif IS_CONSISTENT(ht); //調試信息輸出
if (nKeyLength <= 0) { //添加的是字符串索引的,因此nKeyLength不可能<=0
#if ZEND_DEBUG ZEND_PUTS("zend_hash_update: Can't put in empty key\n"); #endif
return FAILURE; } /** * 檢查是否初始化buckets空間,若沒有初始化則初始化buckets的內存空間 * 爲arBuckets申請內存,爲nTableSize賦值,由於在zend_hash_init裏邊nTableSize設置爲0 */ CHECK_INIT(ht); h = zend_inline_hash_func(arKey, nKeyLength); /* 計算key的hash值 */ nIndex = h & ht->nTableMask; /* 利用掩碼獲得key的實際存儲位置 */ p = ht->arBuckets[nIndex]; /* 取到指定位置的bucket指針 */
while (p != NULL) { /* 若指針不爲空,則表示當前位置已有bucket了 */
if (p->arKey == arKey || ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) { /* 若當前bucket的key和要存入的key相同,那麼須要更新 */
if (flag & HASH_ADD) { /* 若是當前指定是add操做,此時就返回失敗了 */
return FAILURE; } /** * interruptions,打斷,中斷的意思 */ HANDLE_BLOCK_INTERRUPTIONS(); #if ZEND_DEBUG
if (p->pData == pData) { ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n"); HANDLE_UNBLOCK_INTERRUPTIONS(); return FAILURE; } #endif
if (ht->pDestructor) { /* 調用析構函數析構掉原先的值 */ ht->pDestructor(p->pData); } UPDATE_DATA(ht, p, pData, nDataSize); /* 替換爲新的值 */
if (pDest) { *pDest = p->pData; } HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; } p = p->pNext; /* 若當前key和要存入的key不一樣,那麼查找Hash拉鍊的下一個bucket } /* 運行到這裏,表示沒有找到任何已存在的key和要存入的key相同的,那麼申請一個sizeof(bucket)+nKeyLength大小的新空間給key */
//interned用google搜了一下,發現是'字符串駐留'的概念,也沒搞太清楚 //大概就是維護了一個駐留池,會把在編譯期間相同的字符串只保留一份拷貝。
if (IS_INTERNED(arKey)) { p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent); if (!p) { return FAILURE; } p->arKey = arKey; } else { p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent); //柔性數組的概念
if (!p) { return FAILURE; } p->arKey = (const char*)(p + 1); //p+1就是arKey的起始地址
memcpy((char*)p->arKey, arKey, nKeyLength); } p->nKeyLength = nKeyLength; INIT_DATA(ht, p, pData, nDataSize); /* 執行賦值 */ p->h = h; CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); /* 設置亂七八槽的指針 */
if (pDest) { *pDest = p->pData; } HANDLE_BLOCK_INTERRUPTIONS(); CONNECT_TO_GLOBAL_DLLIST(p, ht); /* 將Bucket 加入到HashTable的雙向鏈表中 */ ht->arBuckets[nIndex] = p; HANDLE_UNBLOCK_INTERRUPTIONS(); ht->nNumOfElements++; // 若是HashTable已滿,從新調整HashTable的大小。
ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */
return SUCCESS;
上邊的函數中涉及到宏CHECK_INIT,在Zend_hash.c中定義以下,索引
#define CHECK_INIT(ht) do { \
if (UNEXPECTED((ht)->nTableMask == 0)) { \ (ht)->arBuckets = (Bucket **) pecalloc((ht)->nTableSize, sizeof(Bucket *), (ht)->persistent); \ (ht)->nTableMask = (ht)->nTableSize - 1; \ } \ } while (0)
INIT_DATA宏的定義,
#define INIT_DATA(ht, p, pData, nDataSize); \
if (nDataSize == sizeof(void*)) { \ memcpy(&(p)->pDataPtr, pData, sizeof(void *)); \ (p)->pData = &(p)->pDataPtr; \ } else { \ (p)->pData = (void *) pemalloc_rel(nDataSize, (ht)->persistent);\ if (!(p)->pData) { \ pefree_rel(p, (ht)->persistent); \ return FAILURE; \ } \ memcpy((p)->pData, pData, nDataSize); \ (p)->pDataPtr=NULL; \ }
這裏有一個tricks,PHP判斷數據的大小和一個void指針相同時,就不爲其申請額外的空間,而是將數據copy到pDataPtr字段中,也就是 說,若是你add到HashTable的是一個指針,那麼他直接被保存在pDataPtr字段中,同時pData字段也會保存一份。若是你add到 HashTable的是一個更大的結構,那麼PHP會爲這個結構單獨申請內存空間,將數據copy到這片新申請的內存空間中,而後將pDataPtr設置 爲NULL。