HashTable是Zend引擎中最重要、使用最廣泛的數據結構,它被用來存儲幾乎所有的東西。
1.2.1 數據結構
HashTable數據結構定義如下:
復制代碼 代碼如下:
typedef struct bucket {
ulong h; // 存放hash
uint nKeyLength;
void *pData; // 指向value,是用戶數據的副本
void *pDataPtr;
struct bucket *pListNext; // pListNext和pListLast組成
struct bucket *pListLast; // 整個HashTable的雙鏈表
struct bucket *pNext; // pNext和pLast用于組成某個hash對應
struct bucket *pLast; // 的雙鏈表
char arKey[1]; // key
} Bucket;
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer; /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets; // hash數組
dtor_func_t pDestructor; // HashTable初始化時指定,銷毀Bucket時調用
zend_bool persistent; // 是否采用C的內存分配例程
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable;
復制代碼 代碼如下:
p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);
1.2.2 PHP數組
關于HashTable還有一個疑問沒有回答,就是nNextFreeElement是干什么的?
不同于一般的散列,Zend的HashTable允許用戶直接指定hash值,而忽略key,甚至可以不指定key(此時,nKeyLength為0)。同時,HashTable也支持append操作,用戶連hash值也不用指定,只需要提供value,此時,Zend就用nNextFreeElement作為hash,之后將nNextFreeElement遞增。
HashTable的這種行為看起來很奇怪,因為這將無法按key訪問value,已經完全不是個散列了。理解問題的關鍵在于,PHP數組就是使用HashTable實現的——關聯數組使用正常的k-v映射將元素加入HashTable,其key為用戶指定的字符串;非關聯數組則直接使用數組下標作為hash值,不存在key;而當在一個數組中混合使用關聯和非關聯時,或者使用array_push操作時,就需要用nNextFreeElement了。
再來看value,PHP數組的value直接使用了zval這個通用結構,pData指向的是zval*,按照上一節的介紹,這個zval*將直接存儲在pDataPtr里。由于直接使用了zval,數組的元素可以是任意PHP類型。
數組的遍歷操作,即foreach、each等,是通過HashTable的雙向鏈表來進行的,pInternalPointer作為游標記錄了當前位置。
1.2.3 變量符號表
除了數組,HashTable還被用來存儲許多其他數據,比如,PHP函數、變量符號、加載的模塊、類成員等。
一個變量符號表就相當于一個關聯數組,其key是變量名(可見,使用很長的變量名并不是個好主意),value是zval*。
在任一時刻PHP代碼都可以看見兩個變量符號表——symbol_table和active_symbol_table——前者用于存儲全局變量,稱為全局符號表;后者是個指針,指向當前活動的變量符號表,通常情況下就是全局符號表。但是,當每次進入一個PHP函數時(此處指的是用戶使用PHP代碼創建的函數),Zend都會創建函數局部的變量符號表,并將active_symbol_table指向局部符號表。Zend總是使用active_symbol_table來訪問變量,這樣就實現了局部變量的作用域控制。
但如果在函數局部訪問標記為global的變量,Zend會進行特殊處理——在active_symbol_table中創建symbol_table中同名變量的引用,如果symbol_table中沒有同名變量則會先創建。
1.3 內存和文件
程序擁有的資源一般包括內存和文件,對于通常的程序,這些資源是面向進程的,當進程結束后,操作系統或C庫會自動回收那些我們沒有顯式釋放的資源。
但是,PHP程序有其特殊性,它是基于頁面的,一個頁面運行時同樣也會申請內存或文件這樣的資源,然而當頁面運行結束后,操作系統或C庫也許不會知道需要進行資源回收。比如,我們將php作為模塊編譯到apache里,并且以prefork或worker模式運行apache。這種情況下apache進程或線程是復用的,php頁面分配的內存將永駐內存直到出core。
為了解決這種問題,Zend提供了一套內存分配API,它們的作用和C中相應函數一樣,不同的是這些函數從Zend自己的內存池中分配內存,并且它們可以實現基于頁面的自動回收。在我們的模塊中,為頁面分配的內存應該使用這些API,而不是C例程,否則Zend會在頁面結束時嘗試efree掉我們的內存,其結果通常就是crush。
emalloc()
efree()
estrdup()
estrndup()
ecalloc()
erealloc()
另外,Zend還提供了一組形如VCWD_xxx的宏用于替代C庫和操作系統相應的文件API,這些宏能夠支持PHP的虛擬工作目錄,在模塊代碼中應該總是使用它們。宏的具體定義參見PHP源代碼”TSRM/tsrm_virtual_cwd.h”??赡苣銜⒁獾剑心切┖曛胁]有提供close操作,這是因為close的對象是已打開的資源,不涉及到文件路徑,因此可以直接使用C或操作系統例程;同理,read/write之類的操作也是直接使用C或操作系統的例程。
新聞熱點
疑難解答