亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁/技術(shù)文章
文章詳情頁

PHP內(nèi)核探索 —— 理解Zend里的哈希表

瀏覽:4日期:2022-09-16 14:06:18

在PHP的Zend引擎中,有一個(gè)數(shù)據(jù)結(jié)構(gòu)非常重要,它無處不在,是PHP數(shù)據(jù)存儲的核心,各種常量、變量、函數(shù)、類、對象等都用它來組織,這個(gè)數(shù)據(jù)結(jié)構(gòu)就是HashTable。

HashTable在通常的數(shù)據(jù)結(jié)構(gòu)教材中也稱作散列表,哈希表。其基本原理比較簡單(如果你對其不熟悉,請查閱隨便一本數(shù)據(jù)結(jié)構(gòu)教材或在網(wǎng)上搜索),但PHP的實(shí)現(xiàn)有其獨(dú)特的地方。理解了HashTable的數(shù)據(jù)存儲結(jié)構(gòu),對我們分析PHP的源代碼,特別是Zend Engine中的虛擬機(jī)的實(shí)現(xiàn)時(shí),有很重要的幫助。它可以幫助我們在大腦中模擬一個(gè)完整的虛擬機(jī)的形象。它也是PHP中其它一些數(shù)據(jù)結(jié)構(gòu)如數(shù)組實(shí)現(xiàn)的基礎(chǔ)。

Zend HashTable的實(shí)現(xiàn)結(jié)合了雙向鏈表和向量(數(shù)組)兩種數(shù)據(jù)結(jié)構(gòu)的優(yōu)點(diǎn),為PHP提供了非常高效的數(shù)據(jù)存儲和查詢機(jī)制。

HashTable的數(shù)據(jù)結(jié)構(gòu)

在Zend Engine中的HashTable的實(shí)現(xiàn)代碼主要包括zend_hash.h, zend_hash.c這兩個(gè)文件中。Zend HashTable包括兩個(gè)主要的數(shù)據(jù)結(jié)構(gòu),其一是Bucket(桶)結(jié)構(gòu),另一個(gè)是HashTable結(jié)構(gòu)。Bucket結(jié)構(gòu)是用于保存數(shù)據(jù)的容器,而 HashTable結(jié)構(gòu)則提供了對所有這些Bucket(或桶列)進(jìn)行管理的機(jī)制。

typedef struct bucket { ulong h; /* Used for numeric indexing */ uint nKeyLength; /* key 長度 */ void *pData; /* 指向Bucket中保存的數(shù)據(jù)的指針 */ void *pDataPtr; /* 指針數(shù)據(jù) */ struct bucket *pListNext; /* 指向HashTable桶列中下一個(gè)元素 */ struct bucket *pListLast; /* 指向HashTable桶列中前一個(gè)元素 */ struct bucket *pNext; /* 指向具有同一個(gè)hash值的桶列的后一個(gè)元素 */ struct bucket *pLast; /* 指向具有同一個(gè)hash值的桶列的前一個(gè)元素 */ char arKey[1]; /* 必須是最后一個(gè)成員,key名稱*/} Bucket;

在Zend HashTable中,每個(gè)數(shù)據(jù)元素(Bucket)有一個(gè)鍵名(key),它在整個(gè)HashTable中是唯一的,不能重復(fù)。根據(jù)鍵名可以唯一確定 HashTable中的數(shù)據(jù)元素。鍵名有兩種表示方式。第一種方式使用字符串a(chǎn)rKey作為鍵名,該字符串的長度為nKeyLength。注意到在上面的數(shù)據(jù)結(jié)構(gòu)中arKey雖然只是一個(gè)長度為1的字符數(shù)組,但它并不意味著key只能是一個(gè)字符。實(shí)際上Bucket是一個(gè)可變長的結(jié)構(gòu)體,由于arKey是 Bucket的最后一個(gè)成員變量,通過arKey與nKeyLength結(jié)合可確定一個(gè)長度為nKeyLength的key。這是C語言編程中的一個(gè)比較 常用的技巧。另一種鍵名的表示方式是索引方式,這時(shí)nKeyLength總是0,長整型字段h就表示該數(shù)據(jù)元素的鍵名。簡單的來說,即如果 nKeyLength=0,則鍵名為h;否則鍵名為arKey, 鍵名的長度為nKeyLength。

當(dāng)nKeyLength > 0時(shí),并不表示這時(shí)的h值就沒有意義。事實(shí)上,此時(shí)它保存的是arKey對應(yīng)的hash值。不管hash函數(shù)怎么設(shè)計(jì),沖突都是不可避免的,也就是說不同 的arKey可能有相同的hash值。具有相同hash值的Bucket保存在HashTable的arBuckets數(shù)組(參考下面的解釋)的同一個(gè)索 引對應(yīng)的桶列中。這個(gè)桶列是一個(gè)雙向鏈表,其前向元素,后向元素分別用pLast, pNext來表示。新插入的Bucket放在該桶列的最前面。

在Bucket中,實(shí)際的數(shù)據(jù)是保存在pData指針指向的內(nèi)存塊中,通常這個(gè)內(nèi)存塊是系統(tǒng)另外分配的。但有一種情況例外,就是當(dāng)Bucket保存的數(shù)據(jù)是一個(gè)指針時(shí),HashTable將不會另外請求系統(tǒng)分配空間來保存這個(gè)指針,而是直接將該指針保存到pDataPtr中,然后再將pData指向 本結(jié)構(gòu)成員的地址。這樣可以提高效率,減少內(nèi)存碎片。由此我們可以看到PHP HashTable設(shè)計(jì)的精妙之處。如果Bucket中的數(shù)據(jù)不是一個(gè)指針,pDataPtr為NULL。

HashTable中所有的Bucket通過pListNext, pListLast構(gòu)成了一個(gè)雙向鏈表。最新插入的Bucket放在這個(gè)雙向鏈表的最后。

注意在一般情況下,Bucket并不能提供它所存儲的數(shù)據(jù)大小的信息。所以在PHP的實(shí)現(xiàn)中,Bucket中保存的數(shù)據(jù)必須具有管理自身大小的能力。

typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket *pInternalPointer; Bucket *pListHead; Bucket *pListTail; Bucket **arBuckets; dtor_func_t pDestructor; zend_bool persistent; unsigned char nApplyCount; zend_bool bApplyProtection; #if ZEND_DEBUG int inconsistent; #endif} HashTable;

在HashTable結(jié)構(gòu)中,nTableSize指定了HashTable的大小,同時(shí)它限定了HashTable中能保存Bucket的最大數(shù)量,此 數(shù)越大,系統(tǒng)為HashTable分配的內(nèi)存就越多。為了提高計(jì)算效率,系統(tǒng)自動會將nTableSize調(diào)整到最小一個(gè)不小于nTableSize的2 的整數(shù)次方。也就是說,如果在初始化HashTable時(shí)指定一個(gè)nTableSize不是2的整數(shù)次方,系統(tǒng)將會自動調(diào)整nTableSize的值。即

nTableSize = 2ceil(log(nTableSize, 2)) 或 nTableSize = pow(ceil(log(nTableSize,2)))

例如,如果在初始化HashTable的時(shí)候指定nTableSize = 11,HashTable初始化程序會自動將nTableSize增大到16。

arBuckets是HashTable的關(guān)鍵,HashTable初始化程序會自動申請一塊內(nèi)存,并將其地址賦值給arBuckets,該內(nèi)存大 小正好能容納nTableSize個(gè)指針。我們可以將arBuckets看作一個(gè)大小為nTableSize的數(shù)組,每個(gè)數(shù)組元素都是一個(gè)指針,用于指向 實(shí)際存放數(shù)據(jù)的Bucket。當(dāng)然剛開始時(shí)每個(gè)指針均為NULL。

nTableMask的值永遠(yuǎn)是nTableSize – 1,引入這個(gè)字段的主要目的是為了提高計(jì)算效率,是為了快速計(jì)算Bucket鍵名在arBuckets數(shù)組中的索引。

nNumberOfElements記錄了HashTable當(dāng)前保存的數(shù)據(jù)元素的個(gè)數(shù)。當(dāng)nNumberOfElement大于nTableSize時(shí),HashTable將自動擴(kuò)展為原來的兩倍大小。

nNextFreeElement記錄HashTable中下一個(gè)可用于插入數(shù)據(jù)元素的arBuckets的索引。

pListHead, pListTail則分別表示Bucket雙向鏈表的第一個(gè)和最后一個(gè)元素,這些數(shù)據(jù)元素通常是根據(jù)插入的順序排列的。也可以通過各種排序函數(shù)對其進(jìn)行重 新排列。pInternalPointer則用于在遍歷HashTable時(shí)記錄當(dāng)前遍歷的位置,它是一個(gè)指針,指向當(dāng)前遍歷到的Bucket,初始值是 pListHead。

pDestructor是一個(gè)函數(shù)指針,在HashTable的增加、修改、刪除Bucket時(shí)自動調(diào)用,用于處理相關(guān)數(shù)據(jù)的清理工作。

persistent標(biāo)志位指出了Bucket內(nèi)存分配的方式。如果persisient為TRUE,則使用操作系統(tǒng)本身的內(nèi)存分配函數(shù)為Bucket分配內(nèi)存,否則使用PHP的內(nèi)存分配函數(shù)。具體請參考PHP的內(nèi)存管理。

nApplyCount與bApplyProtection結(jié)合提供了一個(gè)防止在遍歷HashTable時(shí)進(jìn)入遞歸循環(huán)時(shí)的一種機(jī)制。

inconsistent成員用于調(diào)試目的,只在PHP編譯成調(diào)試版本時(shí)有效。表示HashTable的狀態(tài),狀態(tài)有四種:

狀態(tài)值的含義:

HT_IS_DESTROYING 正在刪除所有的內(nèi)容,包括arBuckets本身HT_IS_DESTROYED 已刪除,包括arBuckets本身HT_CLEANING 正在清除所有的arBuckets指向的內(nèi)容,但不包括arBuckets本身HT_OK 正常狀態(tài),各種數(shù)據(jù)完全一致

typedef struct _zend_hash_key { char *arKey; /* hash元素key名稱 */ uint nKeyLength; /* hash 元素key長度 */ ulong h; /* key計(jì)算出的hash值或直接指定的數(shù)值下標(biāo) */} zend_hash_key;

現(xiàn)在來看zend_hash_key結(jié)構(gòu)就比較容易理解了。它通過arKey, nKeyLength, h三個(gè)字段唯一確定了HashTable中的一個(gè)元素。

根據(jù)上面對HashTable相關(guān)數(shù)據(jù)結(jié)構(gòu)的解釋,我們可以畫出HashTable的內(nèi)存結(jié)構(gòu)圖:

PHP內(nèi)核探索 —— 理解Zend里的哈希表

Zend HashTable的實(shí)現(xiàn)

本節(jié)具體介紹一下PHP中HashTable的實(shí)現(xiàn)。以下函數(shù)均取自于zend_hash.c。只要充分理解了上述數(shù)據(jù)結(jié)構(gòu),HashTable實(shí)現(xiàn)的代碼并不難理解。

1. HashTable初始化

HashTable提供了一個(gè)zend_hash_init宏來完成HashTable的初始化操作。實(shí)際上它是通過下面的內(nèi)部函數(shù)來實(shí)現(xiàn)的:

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 >= 0×80000000) {/* prevent overflow */ht->nTableSize = 0×80000000; } else {while ((1U << i) < nSize) { /* 自動調(diào)整nTableSize至2的n次方 */ i++; } ht->nTableSize = 1 << i; /* i的最小值為3,因此HashTable大小最小為8 */ } ht->nTableMask = ht->nTableSize - 1; ht->pDestructor = pDestructor; ht->arBuckets = NULL; ht->pListHead = NULL; ht->pListTail = NULL; ht->nNumOfElements = 0; ht->nNextFreeElement = 0; ht->pInternalPointer = NULL; ht->persistent = persistent; ht->nApplyCount = 0; ht->bApplyProtection = 1; /* 根據(jù)persistent使用不同方式分配arBuckets內(nèi)存,并將其所有指針初始化為NULL*/ /* Uses ecalloc() so that Bucket* == NULL */ if (persistent) {tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));if (!tmp) { return FAILURE;}ht->arBuckets = tmp; } else {tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));if (tmp) { ht->arBuckets = tmp;} } return SUCCESS;}

在以前的版本中,可以使用pHashFunction來指定hash函數(shù)。但現(xiàn)PHP已強(qiáng)制使用DJBX33A算法,因此實(shí)際上pHashFunction這個(gè)參數(shù)并不會用到,保留在這里只是為了與以前的代碼兼容。

2. 增加、插入和修改元素

向HashTable中添加一個(gè)新的元素最關(guān)鍵的就是要確定將這個(gè)元素插入到arBuckets數(shù)組中的哪個(gè)位置。根據(jù)上面對Bucket結(jié)構(gòu)鍵名 的解釋,我們可以知道有兩種方式向HashTable添加一個(gè)新的元素。第一種方法是使用字符串作為鍵名來插入Bucket;第二種方法是使用索引作為鍵 名來插入Bucket。第二種方法具體又可以分為兩種情況:指定索引或不指定索引,指定索引指的是強(qiáng)制將Bucket插入到指定的索引位置中;不指定索引 則將Bucket插入到nNextFreeElement對應(yīng)的索引位置中。這幾種插入數(shù)據(jù)的方法實(shí)現(xiàn)比較類似,不同的只是定位Bucket的方法。

修改HashTable中的數(shù)據(jù)的方法與增加數(shù)據(jù)的方法也很類似。

我們先看第一種使用字符串作為鍵名增加或修改Bucket的方法:

ZEND_API int _zend_hash_add_or_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC){ ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); // 調(diào)試信息輸出 if (nKeyLength <= 0) {#if ZEND_DEBUG ZEND_PUTS(”zend_hash_update: Can’t put in empty keyn”); #endif return FAILURE; } /* 使用hash函數(shù)計(jì)算arKey的hash值 */ h = zend_inline_hash_func(arKey, nKeyLength); /* 將hash值和nTableMask按位與后生成該元素在arBuckets中的索引。讓它和 * nTableMask按位與是保證不會產(chǎn)生一個(gè)使得arBuckets越界的數(shù)組下標(biāo)。 */ nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; /* 取得相應(yīng)索引對應(yīng)的Bucket的指針 */ /* 檢查對應(yīng)的桶列中是否包含有數(shù)據(jù)元素(key, hash) */ while (p != NULL) {if ((p->h == h) && (p->nKeyLength == nKeyLength)) { if (!memcmp(p->arKey, arKey, nKeyLength)) { if (flag & HASH_ADD) { return FAILURE; // 對應(yīng)的數(shù)據(jù)元素已存在,不能進(jìn)行插入操作 } HANDLE_BLOCK_INTERRUPTIONS(); #if ZEND_DEBUG if (p->pData == pData) { ZEND_PUTS(”Fatal error in zend_hash_update: p->pData == pDatan”); HANDLE_UNBLOCK_INTERRUPTIONS(); return FAILURE; } #endif if (ht->pDestructor) { /* 如果數(shù)據(jù)元素存在,對原來的數(shù)據(jù)進(jìn)行析構(gòu)操作 */ ht->pDestructor(p->pData); } /* 用新的數(shù)據(jù)來更新原來的數(shù)據(jù) */ UPDATE_DATA(ht, p, pData, nDataSize); if (pDest) { *pDest = p->pData; } HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; }}p = p->pNext; } /* HashTable中沒有key對應(yīng)的數(shù)據(jù),新增一個(gè)Bucket */ p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent); if (!p) {return FAILURE; } memcpy(p->arKey, arKey, nKeyLength); p->nKeyLength = nKeyLength; INIT_DATA(ht, p, pData, nDataSize); p->h = h; // 將Bucket加入到相應(yīng)的桶列中 CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); if (pDest) {*pDest = p->pData; } HANDLE_BLOCK_INTERRUPTIONS(); // 將Bucket 加入到HashTable的雙向鏈表中 CONNECT_TO_GLOBAL_DLLIST(p, ht); ht->arBuckets[nIndex] = p; HANDLE_UNBLOCK_INTERRUPTIONS(); ht->nNumOfElements++; // 如果HashTable已滿,重新調(diào)整HashTable的大小。 ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */ return SUCCESS;}

因?yàn)檫@個(gè)函數(shù)是使用字符串作為鍵名來插入數(shù)據(jù)的,因此它首先檢查nKeyLength的值是否大于0,如果不是的話就直接退出。然后計(jì)算arKey對應(yīng)的 hash值h,將其與nTableMask按位與后得到一個(gè)無符號整數(shù)nIndex。這個(gè)nIndex就是將要插入的Bucket在arBuckets數(shù) 組中的索引位置。

現(xiàn)在已經(jīng)有了arBuckets數(shù)組的一個(gè)索引,我們知道它包括的數(shù)據(jù)是一個(gè)指向Bucket的雙向鏈表的指針。如果這個(gè)雙向鏈表不為空的話我們首先檢查 這個(gè)雙向鏈表中是否已經(jīng)包含了用字符串a(chǎn)rKey指定的鍵名的Bucket,這樣的Bucket如果存在,并且我們要做的操作是插入新Bucket(通過 flag標(biāo)識),這時(shí)就應(yīng)該報(bào)錯(cuò) – 因?yàn)樵贖ashTable中鍵名不可以重復(fù)。如果存在,并且是修改操作,則使用在HashTable中指定了析構(gòu)函數(shù)pDestructor對原來的 pData指向的數(shù)據(jù)進(jìn)行析構(gòu)操作;然后將用新的數(shù)據(jù)替換原來的數(shù)據(jù)即可成功返回修改操作。

如果在HashTable中沒有找到鍵名指定的數(shù)據(jù),就將該數(shù)據(jù)封裝到Bucket中,然后插入HashTable。這里要注意的是如下的兩個(gè)宏:

CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex])CONNECT_TO_GLOBAL_DLLIST(p, ht)

前者是將該Bucket插入到指定索引的Bucket雙向鏈表中,后者是插入到整個(gè)HashTable的Bucket雙向鏈表中。兩者的插入方式也不同,前者是將該Bucket插入到雙向鏈表的最前面,后者是插入到雙向鏈表的最末端。

下面是第二種插入或修改Bucket的方法,即使用索引的方法:

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){uint nIndex;Bucket *p;IS_CONSISTENT(ht);if (flag & HASH_NEXT_INSERT) {h = ht->nNextFreeElement;}nIndex = h & ht->nTableMask;p = ht->arBuckets[nIndex];// 檢查是否含有相應(yīng)的數(shù)據(jù)while (p != NULL) {if ((p->nKeyLength == 0) && (p->h == h)) {if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) {return FAILURE;}//// …… 修改Bucket數(shù)據(jù),略//if ((long)h >= (long)ht->nNextFreeElement) {ht->nNextFreeElement = h + 1;}if (pDest) {*pDest = p->pData;}return SUCCESS;}p = p->pNext;}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;}CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);HANDLE_BLOCK_INTERRUPTIONS();ht->arBuckets[nIndex] = p;CONNECT_TO_GLOBAL_DLLIST(p, ht);HANDLE_UNBLOCK_INTERRUPTIONS();if ((long)h >= (long)ht->nNextFreeElement) {ht->nNextFreeElement = h + 1;}ht->nNumOfElements++;ZEND_HASH_IF_FULL_DO_RESIZE(ht);return SUCCESS;}

flag標(biāo)志指明當(dāng)前操作是HASH_NEXT_INSERT(不指定索引插入或修改), HASH_ADD(指定索引插入)還是HASH_UPDATE(指定索引修改)。由于這些操作的實(shí)現(xiàn)代碼基本相同,因此統(tǒng)一合并成了一個(gè)函數(shù),再用flag加以區(qū)分。

本函數(shù)基本與前一個(gè)相同,不同的是如果確定插入到arBuckets數(shù)組中的索引的方法。如果操作是HASH_NEXT_INSERT,則直接使用nNextFreeElement作為插入的索引。注意nNextFreeElement的值是如何使用和更新的。

3. 訪問元素

同樣,HashTable用兩種方式來訪問元素,一種是使用字符串a(chǎn)rKey的zend_hash_find();另一種是使用索引的訪問方式zend_hash_index_find()。由于其實(shí)現(xiàn)的代碼很簡單,分析工作就留給讀者自已完成。

4. 刪除元素

HashTable刪除數(shù)據(jù)均使用zend_hash_del_key_or_index()函數(shù)來完成,其代碼也較為簡單,這里也不再詳細(xì)分析。需要的是注意如何根據(jù)arKey或h來計(jì)算出相應(yīng)的下標(biāo),以及兩個(gè)雙向鏈表的指針的處理。

5. 遍歷元素

/* This is used to recurse elements and selectively delete certain entries* from a hashtable. apply_func() receives the data and decides if the entry* should be deleted or recursion should be stopped. The following three* return codes are possible:* ZEND_HASH_APPLY_KEEP - continue* ZEND_HASH_APPLY_STOP - stop iteration* ZEND_HASH_APPLY_REMOVE - delete the element, combineable with the former*/ZEND_API void zend_hash_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC){Bucket *p;IS_CONSISTENT(ht);HASH_PROTECT_RECURSION(ht);p = ht->pListHead;while (p != NULL) {int result = apply_func(p->pData TSRMLS_CC);if (result & ZEND_HASH_APPLY_REMOVE) {p = zend_hash_apply_deleter(ht, p);} else {p = p->pListNext;}if (result & ZEND_HASH_APPLY_STOP) {break;}}HASH_UNPROTECT_RECURSION(ht);}

因?yàn)镠ashTable中所有Bucket都可以通過pListHead指向的雙向鏈表來訪問,因此遍歷HashTable的實(shí)現(xiàn)也比較簡單。這里值得一 提的是對當(dāng)前遍歷到的Bucket的處理使用了一個(gè)apply_func_t類型的回調(diào)函數(shù)。根據(jù)實(shí)際需要,該回調(diào)函數(shù)返回下面值之一:

ZEND_HASH_APPLY_KEEPZEND_HASH_APPLY_STOPZEND_HASH_APPLY_REMOVE

它們分別表示繼續(xù)遍歷,停止遍歷或刪除相應(yīng)元素后繼續(xù)遍歷。

還有一個(gè)要注意的問題就是遍歷時(shí)的防止遞歸的問題,也就是防止對同一個(gè)HashTable同時(shí)進(jìn)行多次遍歷。這是用下面兩個(gè)宏來實(shí)現(xiàn)的:

HASH_PROTECT_RECURSION(ht)HASH_UNPROTECT_RECURSION(ht)

其主要原理是如果遍歷保護(hù)標(biāo)志bApplyProtection為真,則每次進(jìn)入遍歷函數(shù)時(shí)將nApplyCount值加1,退出遍歷函數(shù)時(shí)將nApplyCount值減1。開始遍歷之前如果發(fā)現(xiàn)nApplyCount > 3就直接報(bào)告錯(cuò)誤信息并退出遍歷。

上面的apply_func_t不帶參數(shù)。HashTable還提供帶一個(gè)參數(shù)或可變參數(shù)的回調(diào)方式,對應(yīng)的遍歷函數(shù)分別為:

typedef int (*apply_func_arg_t)(void *pDest,void *argument TSRMLS_DC);void zend_hash_apply_with_argument(HashTable *ht,apply_func_arg_t apply_func, void *data TSRMLS_DC);typedef int (*apply_func_args_t)(void *pDest,int num_args, va_list args, zend_hash_key *hash_key);void zend_hash_apply_with_arguments(HashTable *ht,apply_func_args_t apply_func, int numargs, …);

除了上面提供的幾種提供外,還有許多其它操作HashTable的API。如排序、HashTable的拷貝與合并等等。只要充分理解了上述HashTable的數(shù)據(jù)結(jié)構(gòu),理解這些代碼并不困難。

標(biāo)簽: PHP
相關(guān)文章:
主站蜘蛛池模板: 1819高清欧美xx| 视频一区二区免费 | 欧美黄色一级 | 一级做a爰片久久毛片看看 一级做a爰片久久毛片美女 | 麻豆黄色 | 亚洲一级在线 | 成年偏黄网站站免费 | 精品一区二区三区视频在线观看免 | 国产精品极品美女免费观看 | 麻豆视频在线免费 | 日韩国产免费一区二区三区 | 免费爱爱视频网站 | 国产乱理伦片在线观看 | 精品国产一区二区三区不卡 | 欧美一级久久 | 日本久草视频 | 狠狠色丁香婷婷久久综合2021 | 麻豆一区二区免费播放网站 | 亚洲精品综合网在线8050影院 | 国产精品成人一区二区三区 | 分享一个无毒不卡免费国产 | 色综合精品 | 中文字幕在线日韩 | 国产高清www免费视频 | 黄片毛片一级片 | zzijzzij亚洲日本护士 | 日韩三级一区二区 | 国产精品一区二区三区久久 | 亚洲综合激情另类图片专区 | 2020年国产一国产一级毛卡片 | 韩国精品一区二区三区四区五区 | 国产久热美女福利视频 | 成人不卡在线 | 日韩欧美制服 | 色图在线观看 | 正在播放一区 | 青草青草伊人精品视频 | 黄色一级片性生活 | 日本免费大黄在线观看 | 中国欧美一级毛片免费 | 一级毛片成人免费看免费不卡 |