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; } }
最后附上本身的关联数组实现方法,各位有兴趣的能够下载来看看。