深入PHP中的HashTable結構詳解_php技巧

來源:互聯網
上載者:User

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;


總的來說,Zend的HashTable是一種鏈表散列,同時也為線性遍曆進行了最佳化,圖示如下:


HashTable中包含兩種資料結構,一個鏈表散列和一個雙向鏈表,前者用於進行快速鍵-值查詢,後者方便線性遍曆和排序,一個Bucket同時存在於這兩個資料結構中。
關於該資料結構的幾點解釋:
鏈表散列中為什麼使用雙向鏈表?
一般的鏈表散列只需要按key進行操作,只需要單鏈表就夠了。但是,Zend有時需要從鏈表散列中刪除給定的Bucket,使用雙鏈表可以非常高效的實現。
nTableMask是幹什麼的?
這個值用於hash值到arBuckets數組下標的轉換。當初始化一個HashTable,Zend首先為arBuckets數組分配nTableSize大小的記憶體,nTableSize取不小於使用者指定大小的最小的2^n,即二進位的10*。nTableMask = nTableSize – 1,即二進位的01*,此時h & nTableMask就恰好落在 [0, nTableSize – 1] 裡,Zend就以其為index來訪問arBuckets數組。
pDataPtr是幹什麼的?
通常情況下,當使用者插入一個索引值對時,Zend會將value複製一份,並將pData指向value副本。複製操作需要調用Zend內部常式 emalloc來分配記憶體,這是個非常耗時的操作,並且會消耗比value大的一塊記憶體(多出的記憶體用於存放cookie),如果value很小的話,將會造成較大的浪費。考慮到HashTable多用於存放指標值,於是Zend引入pDataPtr,當value小到和指標一樣長時,Zend就直接將其複製到pDataPtr裡,並且將pData指向pDataPtr。這就避免了emalloc操作,同時也有利於提高Cache命中率。
arKey大小為什麼只有1?為什麼不使用指標管理key?
arKey是存放key的數組,但其大小卻只有1,並不足以放下key。在HashTable的初始化函數裡可以找到如下代碼:

複製代碼 代碼如下:

  p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);

可見,Zend為一個Bucket分配了一塊足夠放下自己和key的記憶體,上半部分是Bucket,下半部分是key,而arKey“恰好”是Bucket的最後一個元素,於是就可以使用arKey來訪問key了。這種手法在記憶體管理常式中最為常見,當分配記憶體時,實際上是分配了比指定大小要大的記憶體,多出的上半部分通常被稱為cookie,它儲存了這塊記憶體的資訊,比如塊大小、上一塊指標、下一塊指標等,baidu的Transmit程式就使用了這種方法。
不用指標管理key,是為了減少一次emalloc操作,同時也可以提高Cache命中率。另一個必需的理由是,key絕大部分情況下是固定不變的,不會因為key變長了而導致重新分配整個Bucket。這同時也解釋了為什麼不把value也一起作為數組分配了——因為value是可變的。

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或作業系統的常式。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.