PHP是一門入門容易,使用範圍普遍的語言,以其靈活性以及web後端開發被不少人熟知,也被不少人戲稱「PHP是世界上最好的語言」。本人是一名「忠實」的PHPer,相信用過PHP的程序員都會體會到PHP數組的靈活性,相對傳統的C語言,使用起來非常方便,擁有關聯數組(key值能夠是字符串),不須要預約義數組空間大小,關聯數組,不須要指定key的快速索引賦值等等便利方法,這段時間研究了一下PHP數組的底層結構,並總結分析,裏面含有一些我本身的猜測,若有錯誤請指出。
哈希結構是一種很是重要的數據結構,他是一種經過key映射到value的結構,因爲其特性,能夠在大部分的狀況下讓查找和插入的效率達到O(1),在不少語言或者系統裏面都有顯性得體現出來,具體的實現思路有不少種。詳細的介紹能夠看個人博客數據結構之哈希結構php
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數組的指針的指針。 }
可能首次去看數據結構可能會以爲有點難受,密密麻麻的一堆東西,下面我會一個個分析數據字段。程序員
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。
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; } }
最後附上本身的關聯數組實現方法,各位有興趣的能夠下載來看看。