剖析PHP底層數組是如何實現的

  PHP是一門入門容易,使用範圍普遍的語言,以其靈活性以及web後端開發被不少人熟知,也被不少人戲稱「PHP是世界上最好的語言」。本人是一名「忠實」的PHPer,相信用過PHP的程序員都會體會到PHP數組的靈活性,相對傳統的C語言,使用起來非常方便,擁有關聯數組(key值能夠是字符串),不須要預約義數組空間大小,關聯數組,不須要指定key的快速索引賦值等等便利方法,這段時間研究了一下PHP數組的底層結構,並總結分析,裏面含有一些我本身的猜測,若有錯誤請指出。

1.PHP的數組底層結構

  哈希結構是一種很是重要的數據結構,他是一種經過key映射到value的結構,因爲其特性,能夠在大部分的狀況下讓查找和插入的效率達到O(1)在不少語言或者系統裏面都有顯性得體現出來,具體的實現思路有不少種。詳細的介紹能夠看個人博客數據結構之哈希結構php

  PHP的數組是用鏈地址法的哈希結構去實現的,鏈表是雙向鏈表,這樣既能夠動態分配數組空間,也能夠經過key值去計算hash值去訪問對應的元素,是一種很是高效的數據結構。
  下面是PHP  Bucket的結構,Bucket是一個基本結點的結構,Bucket是以存放基本元素的容器,能夠簡單理解爲數組元素的房子。
typedef struct Bucket{
   ulong h;//哈希值
   uint nKeyLength; //key的長度,若是key是整形,則此項不須要賦值
   Bucket*  pNext;   //該桶後面的桶,衝突處理的桶
   Bucket*  pLast;   //該桶前面的桶,衝突處理的桶
   Bucket*  pListNext;  //用以記錄數組的順序,該元素前一個元素。
   Bucket*  pListLast;  //用以記錄數組的順序,該元素後一個元素。
   const char * pData; //模擬記錄PHP數據,原來是void *pData和 void *pDataPtr
   char arKey[1]           //記錄key,之因此是[1]是由於這是柔性成員,具體能夠百度C99柔性成員
}Bucket;

  下面是PHP  HashTable結構,HashTable是用以存儲Bucket數組和Bucket信息的哈希表結構,採用雙向鏈表的拉鍊法結構。html

typedef struct HashTable{
   uint nTableSize;   //哈希表的大小
   uint nTableMask;  //哈希表掩碼,用以矯正過長的哈希值
   ulong nNumOfElements;  //記錄當前哈希表存儲了多少個元素,用count($arr)其實就是取出hash表的這個數據
   ulong NextFreeELement;  //記錄下一個空閒位置的索引位置,$arr[]=$value裏的$value就會放到該空間。
   Bucket*  pListHead; //記錄PHP數組的第一個元素
   Bucket*  pLstTail;    //記錄PHP數組的最後一個元素
   Bucket*  pInternalPointer; //記錄當前哈希表指向的Bucket,在foreach,current,next,prev等等會用到,
   Bucket**  arBuckets; //指向存儲實際Hash數組的指針的指針。
}

  可能首次去看數據結構可能會以爲有點難受,密密麻麻的一堆東西,下面我會一個個分析數據字段。程序員

2.Bucket結構體

  1.h(哈希值)web

  經過key映射的哈希值(未通過糾正)h,爲了讓不一樣key值均勻分配到哈希表的各個位置,必需要有一個好的哈希函數,而PHP選用的是time33算法,也就是下面的算法(簡化版)。算法

ulong hash(const char* key){
   ulong hash;
   for(int i=0;key[i];i++){
       hash=hash*33+key[i];
   }
   return hash;
}

  固然啦在PHP的具體實現細節又會有點不一樣,可是原理是差很少的。後端

  2.nKeyLength(字符的個數)數組

  若是使用的是關聯索引,那麼此處nKeyLength就是字符的個數,好比說$arr['key']='value' ,那麼這個值就爲3,若是是索引數組,此字段就不會用上。數據結構

  3.pNext pLast (記錄該桶的先後桶)函數

  繼續引用百度的圖,相似於下面的哈希表,拿元素337來講,他的pNext指向353的位置,pLast指向1的位置,只不過下面是單向鏈表,沒有看到當前元素指向前一個元素。ui

 

  4.pListNext(記錄該桶在數據上的後元素)

  這個字段從命名意思就能夠看出,是鏈表的指向後繼元素的指針。好比說做以下賦值。guangdong的pListNext指向beijing,beijing的pListNext指向shanghai.....

$arr[2]="guangdong";
$arr[1]="beijing";
$arr[3]="shanghai";
$arr[4]="zhejiang";

  因此你若是用foreach去遍歷數組,會發現一個很有趣的現象。輸出的結果以下,竟然不是按照數組下標1,2,3,4順序去輸出,其實只要你理解了PHP的存儲數組的數據結構你就很明白了。

2 => "guangdong"
1 => "beijing"
3 => "shanghai"
4 => "zhejiang"

  他的數據結構以下圖顯示,第一個元素是guangdong,而後來個元素beijing,因而guangdong的pListLast指向beijing,後面的元素同理。而foreach遍歷會從第一個元素(也就是pListHead指向的Bucket,詳看下文HashTable的介紹)去輸出,而後再指向下一個元素,所以輸出的順序不是按照下標來的,而是按照賦值順序來的,這也是爲何foreach遍歷數組要比for遍歷要快的緣由,由於for每次查找元素都要去作一次哈希映射查找對應下標的Bucket,而foreach只須要遍歷Bucket鏈表就行了。pListLast與pListNext同理,只是指向前一個數組元素。

  5.arKey(用以存儲key值)

    這是一個c99柔性成員,若是須要深究能夠百度查查c的柔性成員,若是這一個關聯數組,這個arKey就是存儲對應的key值。$arr['abc']='value';那麼arKey存儲的就是abc。

3.HashTable結構體

  1.nTableSize(哈希表的大小)

  這是哈希表的分配Bucket空間的大小,默認會分配8個Bucket空間,當存儲元素個數大於8個就會存儲16個,如此下去,存儲的個數爲2x大小,即8,16,32,64...

   2.nTableMask(糾正掩碼)

  用以糾正過長的哈希值,值爲nTableSize-1,好比說一個我有一個字符通過哈希函數得出值爲9,可是nTableSize爲8,那該怎麼辦呢,存放到第1個位置吧,計算方法就是9   mod 8,可是在計算機裏面下標是從0開始,所以咱們會使用&運算得出結果,9&7=1。

  3.nNumOfElements(數組元素個數)

  用以統計數組元素的個數,PHP的count()元素其實就是獲取這個值。

  4.NextFreeElement(下一個空閒的元素)

  用以存儲下一個空閒的元素的值。當你的數組是索引數組,用到$arr[]=value賦值就會用到,若是你上次賦值的元素下標是100,那麼NextFreeELement就爲101了。無關你的元素個數。

  5.pListHead(鏈表的頭部元素)

  這個Bucket指針從名字就能夠看出來,用以指向鏈表的頭部元素,例如你給一個數組第一次附上一個值$arr[]=value1,那麼這個指針就是指向value1。

  6.pListTail(鏈表的尾部元素)

  原理同上,只是指向尾部元素,每次來一個新的數組元素,pListTail就會指向它。

  7.nInternalPointer(用以指向內部指向的元素)

  若是咱們用foreach遍歷數組,這個指針就會指向當前遍歷的元素,用以保存當前指向記錄。用到此項的還有current(),next(),prev()函數。

   8.arBuckets(用以存儲Bucket在C的內部數組)

  此項爲指針的指針,能夠用於操做Bucket數組。

  下面列出一副圖來講明PHP的數組結構(爲版面清晰忽略了兩種指向前一個Bucket的指針:pListLast,pLast)。

   最後我大概猜測一下foreach函數的執行過程,首先是將nInternalPointer指向HashTable的第一個Buckets,也就是pListHead,若是不爲空則輸出該元素,而後nInternaPointer指向該Bucket的下一個元素,也就是pListNext,如此循環下去。

void foreach_print(HashTable *ht){
    // 指向數組的頭元素
    ht->pInternalPointer=ht->pListHead;
    // 若是不空則循環遍歷下去
    while(ht->pInternalPointer){
        printf("[%s][%s]\n", ht->pInternalPointer->arKey,ht->pInternalPointer->pData);
        // 而後指向下一個元素
        ht->pInternalPointer = ht->pInternalPointer->pListNext;
    }
}

   最後附上本身的關聯數組實現方法,各位有興趣的能夠下載來看看。

   點擊下載

相關文章
相關標籤/搜索