PHP是一门入门容易,使用范围广泛的语言,以其灵活性以及web后端开发被很多人熟知,也被很多人戏称“PHP是世界上最好的语言”。本人是一名“忠实”的PHPer,相信用过PHP的程序员都会体会到PHP数组的灵活性,相对传统的C语言,使用起来很是方便,拥有关联数组(key值可以是字符串),不需要预定义数组空间大小,关联数组,不需要指定key的快速索引赋值等等便利方法,这段时间研究了一下PHP数组的底层结构,并总结分析,里面含有一些我自己的猜想,如有错误请指出。

1.PHP的数组底层结构

  哈希结构是一种非常重要的数据结构,他是一种通过key映射到value的结构,由于其特性,可以在大部分的情况下让查找和插入的效率达到O(1),在很多语言或者系统里面都有显性的体现出来,具体的实现思路有很多种。

  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信息的哈希表结构,采用双向链表的拉链法结构。

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(哈希值)

  通过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的位置,只不过下面是单向链表,没有看到当前元素指向前一个元素。

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;    }}

作者:大傻逼

出处:http://www.cnblogs.com/s-b-b/

尝试引用非结构体数组的字段_剖析PHP底层数组是如何实现的相关推荐

  1. 结构体内容引用自非结构体数组对象axes(handles.axes1)

    Matlab结构体内容引用自非结构体数组对象 matlab的gui报错axes(handles.axes1) 如何解决 起因 代码 matlab的gui报错axes(handles.axes1) 废话 ...

  2. 【C语言】关于结构体最后的长度为0或1数组的思考

    需要引起注意的:ISO/IEC 9899-1999里面,这么写是非法的,这个仅仅是GNU C的扩展,gcc可以允许这一语法现象的存在.但最新的C/C++不知道是否可以,我没有测试过.(C99允许.微软 ...

  3. Matlab 结构体之间相同字段的赋值

    Matlab 结构体之间相同字段的赋值 假设有两个结构体 main_subject 和 all_subject .其中 main_subject 中包含了某位同学的主修成绩和学号姓名信息,需要将这些信 ...

  4. c语言 结构成员 变量引用,C++结构体变量的引用

    在定义了结构体变量以后,当然可以引用这个变量,常用的方法有以下几种. 1) 可以将一个结构体变量的值赋给另一个具有相同结构的结构体变量. 如上面的student1和student2都是student类 ...

  5. ANSYS FLUENT非结构体网格数值计算及后处理——换热器

    读入网格 Step1.打开FLUENT 双击FLUENT软件. Step2.定义求解器参数 在Dimension栏选择3D求解器,其余保持默认设置,单击OK按钮. Step3.读入网格 选择File→ ...

  6. 相互引用的结构体的定义

    首先声明,简单的头文件重复包含问题不在此文的讨论范围之内,此问题可以通过下面的文件格式进行解决. #ifndef __filename_h__ #define __filename_h__ /* co ...

  7. matlab结构体如何引用,matlab结构体数组引用

    函数来构造 table 对象 o 通过转换函数构造 table 对象 访问 table 中的数据 MATLAB 常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和 结构体数组.... 结构 ...

  8. 【结构体】 结构体引用、结构体数组指针、包含结构的结构体

    目录 一.概念.变量的定义 初识结构体 结构体变量的定义.引用结构体类型的初始化 二.结构体的数组 认识结构体数组 初始化结构体数组 三.结构体指针 初识结构体指针 指向结构体数组的指针 结构体作为函 ...

  9. 未来的C#之只读引用与结构体

    C++中提供了const特性,使用该特性定义的参数,其所引用的参数或对象将不会被调用函数修改(当然const还提供了更多的特性,参见"Const正确性").在新的建议中,C#也将提 ...

最新文章

  1. 搭建服务器环境 安装jdk、mysql、Tomcat 以及配置https 记录
  2. 前段第44天------前端、前端三剑客、HTML
  3. Python 列表(List)操作方法详解
  4. 进程调度算法Java代码
  5. 计算机博士美国学校推荐,留学随笔:一位计算机博士留学美国的感悟
  6. Jsoup使用DOM方法来遍历一个文档
  7. C#LeetCode刷题之#859-亲密字符串​​​​​​​​​​​​​​(Buddy Strings)
  8. 2004-7-1+ 用户控件(动态加载)
  9. pytest执行时,报report.html错误
  10. android多个柱状图和折线图,RecyclerView 实现柱状图和折线图
  11. c语言小蜜蜂编程题,C语言经典题目“小蜜蜂“代码.docx
  12. 日语从0到N2语法(一)判定句+疑问句分类+存在句+形容词
  13. 如何解决“由于无法验证发行者,所以WINDOWS已经阻止此软件”
  14. HTTP(一)HTTP响应的过程
  15. 深夜来一发,拿走不谢
  16. 扫码进入微信小程序使用WIFI进行UDP通信发送消息
  17. 基于Bootstrap的后台管理系统模板。AceAdmin停更前最后的两个版本
  18. JAVA纺织代加工车间生产状态监测系统计算机毕业设计Mybatis+系统+数据库+调试部署
  19. 哈理工OJ 1490 咒语(BFS广度优先搜索)
  20. Excel折线图设置坐标轴起点不为0

热门文章

  1. mongodb objetcid_mongodb(1)
  2. 递归查找所有子代id php,PHP递归函数:从父ID获取子ID
  3. python工厂模式 简书_[Python设计模式] 01 - 简单工厂模式
  4. Python字典中 get() 函数的使用
  5. 关于Tomcat的部署
  6. 深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)
  7. Linux内存管理:kmemcheck介绍
  8. TCP/IP网络协议栈:以太网数据包结构、802.3、MTU
  9. (2)Linux进程调度器-CPU负载
  10. 汇编:LEA(取偏移地址)、LES