php底層原理之陣列實現

許錚的成長之路發表於2019-03-31

陣列是PHPer最常用的資料型別,同時php容易上手也得益於其強大的陣列,但是陣列在php中是如何實現的呢?

首先,我們還是先了解下相關的資料結構,為下面的內容打好基礎

雜湊表

雜湊表,顧名思義,即將不同的關鍵字對映到不同單元的一種資料結構。而將不同關鍵字對映到不同單元的方法就叫做雜湊函式

理想情況下,經過雜湊函式處理,關鍵字和單元是會進行一一對應的;但是如果關鍵字值足夠多的情況下,就容易出現多個關鍵字對映到同一單元的情況,即出現雜湊衝突

雜湊衝突的解決方案,要麼使用連結法,要麼使用開放定址法

連結法
即當不同的關鍵字對映到同一單元時,在同一單元內使用連結串列來儲存這些關鍵字

開放定址法
即當插入資料時,如果發現關鍵字被對映到的單元存在資料了,說明發生了衝突,就繼續尋找下一個單元,直到找到可用單元為止

而因為開放定址法方案屬於佔用其他關鍵字對映單元的位置,所以後續的關鍵字更容易出現雜湊衝突,因此容易出現效能下降

連結串列

既然上面提到了連結串列,這裡我們簡單聊一下連結串列的基礎知識。連結串列分為很多種型別,常用的資料結構包括:佇列,棧,雙向連結串列等

連結串列,就是由不同的連結串列節點組成的一種資料結構。連結串列節點一般由元素+指向下一節點的指標組成。而雙向連結串列,顧名思義,則是由指向上一節點的指標+元素+指向下一節點的指標組成

對於資料結構的內容,我們不過多展開,我們之後會有專門的內容去詳細介紹資料結構

php陣列

php解決雜湊衝突的方式是使用了連結法,所以php陣列是由雜湊表+連結串列實現,準確來說,是由雜湊表+雙向連結串列實現

內部結構-雜湊表

HashTable結構體主要用來存放雜湊表的基本資訊

typedef struct _hashtable { 
    uint nTableSize;        // hash Bucket的大小,即雜湊表的容量,最小為8,以2x增長。
    uint nTableMask;        // nTableSize-1 , 索引取值的優化
    uint nNumOfElements;    // hash Bucket中當前存在的元素個數,count()函式會直接返回此值 
    ulong nNextFreeElement; // 下一個可使用的數字鍵值
    Bucket *pInternalPointer;   // 當前遍歷的指標(foreach比for快的原因之一)
    Bucket *pListHead;          // 儲存整個雜湊表的頭元素指標
    Bucket *pListTail;          // 儲存整個雜湊表的尾元素指標
    Bucket **arBuckets;         // 儲存hash陣列
    dtor_func_t pDestructor;    // 在刪除元素時執行的回撥函式,用於資源的釋放
    zend_bool persistent;       //指出了Bucket記憶體分配的方式。如果persisient為TRUE,則使用作業系統本身的記憶體分配函式為Bucket分配記憶體,否則使用PHP的記憶體分配函式。
    unsigned char nApplyCount; // 標記當前hash Bucket被遞迴訪問的次數(防止多次遞迴)
    zend_bool bApplyProtection;// 標記當前hash桶允許不允許多次訪問,不允許時,最多隻能遞迴3次
#if ZEND_DEBUG
    int inconsistent;
#endif
} HashTable;
複製程式碼

Bucket結構體則用於儲存資料的具體內容

typedef struct bucket {
    ulong h;            // 對char *key進行hash後的值,或者是使用者指定的數字索引值
    uint nKeyLength;    // hash關鍵字的長度,如果陣列索引為數字,此值為0
    void *pData;        // 指向value,一般是使用者資料的副本,如果是指標資料,則指向pDataPtr
    void *pDataPtr;     // 如果是指標資料,此值會指向真正的value,同時上面pData會指向此值
    struct bucket *pListNext;   // 指向整個雜湊表的該單元的下一個元素
    struct bucket *pListLast;   // 指向整個雜湊表的該單元的上一個元素
    struct bucket *pNext;       // 指向由於雜湊衝突導致存放在同一個單元的連結串列中的下一個元素
    struct bucket *pLast;       // 指向由於雜湊衝突導致存放在同一個單元的連結串列中的上一個元素
    // 儲存當前值所對於的key字串,這個欄位只能定義在最後,實現變長結構體
    char arKey[1];              
} Bucket;
複製程式碼

其中Bucket結構體內有指向使用者資料的pData元素,其實是指向了之前我們介紹的變數zval結構體,這也是為什麼當建立陣列時,會出現陣列元素+1的變數容器。不瞭解變數底層相關知識的,請檢視我之前的文章:

php底層原理之變數(一)
php底層原理之變數(二)

雜湊表內部結構關係圖

雜湊表內部結構關係圖
注:圖片來源於網路

從上圖我們可以看出,Bucket在存放資料的時候,如果存在雜湊衝突,則將多個關鍵字對映到連結串列中,由此組成了雙向連結串列

總結

今天,我們以陣列作為切入點,簡單瞭解了下基本的資料結構:雜湊表和連結串列;並且瞭解了陣列的底層實現,即雜湊表+雙向連結串列。其實雜湊表作為php中最重要的資料結構,用處很廣。變數的符號表,函式列表等都是用雜湊表來儲存的,感興趣的同學可以看我之前的文章來了解相關知識

相關文章