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[] //记录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=;key[i];i++){
hash=hash*+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;
}
}

  最后附上自己的关联数组实现方法,各位有兴趣的可以下载来看看。

  点击下载

从PHP底层源码去深入理解数组,并用C模拟PHP关联数组(原创)的更多相关文章

  1. 硬核剖析Java锁底层AQS源码,深入理解底层架构设计

    我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...

  2. Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...

  3. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  4. JDK部分源码阅读与理解

    本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK ...

  5. 从源码角度深入理解Toast

    Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(", Toast.LENGTH_LONG).show(); 但是我们经常会遇到这 ...

  6. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  7. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

  8. 2018.11.20 Struts2中对结果处理方式分析&struts2内置的方式底层源码剖析

    介绍一下struts2内置帮我们封装好的处理结果方式也就是底层源码分析 这是我们的jar包里面找的位置目录 打开往下拉看到result-type节点 name那一列就是我们的type类型取值 上一篇博 ...

  9. BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享

    BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...

随机推荐

  1. WPF 后台数据触发改变界面状态-心跳实现

    今年做的一个上位机工控WPF项目,做个小小的总结把,以后随时来找 请不要带血乱喷,我只是菜鸟.___by 鲍队 类似于这样子的;大致的意思是:一个代码变量,通过改变变量的值,绑定这个变量的这个圆颜色也 ...

  2. FastDFS 安装及使用

    FastDFS 安装及使用 2012-11-17 13:10:31|  分类: Linux|举报|字号 订阅     Google了一下,流行的开源分布式文件系统有很多,介绍如下:   mogileF ...

  3. Maven远程仓库的认证

    大部分远程仓库无须认证就可以访问,但有时处于安全方面的考虑,我们需要提供认证信息才能访问一些远程仓库.为了防止非法的仓库访问,管理员为每个仓库提供了一组用户名及密码. 这时,为了能让Maven访问仓库 ...

  4. python 学习笔记 -logging模块(日志)

    模块级函数 logging.getLogger([name]):返回一个logger对象,如果没有指定名字将返回root loggerlogging.debug().logging.info().lo ...

  5. jdk链表笔记

    LinkedList LinkedList是双链表,并且有头尾指针 数据结构 public class LinkedList extends AbstractSequentialList implem ...

  6. 《.NET开发资源大全》

    目录 API 应用框架(Application Frameworks) 应用模板(Application Templates) 人工智能(Artificial Intelligence) 程序集处理( ...

  7. wamp 服务器安装问题 及cmd常用命令 和 php mysql数据库常用cmd命令集

    1   官网下载wamp软件包,根据提示安装 2   目录结构:   wamp:   bin/为套件目录 包括mysql apache php log   日志记录 alias 配置 apps 数据库 ...

  8. 响应式手机商城页面顶部样式HTML代码

    本特效支持PC浏览器和触屏浏览器. 效果展示 http://hovertree.com/texiao/bootstrap/8/ 手机扫描二维码体验效果: HTML代码如下: <!DOCTYPE ...

  9. ExtPB.Net:窗体应用技巧(2)在树形导航下打开弹出的win窗口

    ExtPB.Net的demo程序有个树形导航菜单,里面的菜单打开的窗口放在右边的TabStrip控件中.我们可以设计win通过导航打开,但有时我们希望以弹出窗口的形式打开它,但怎么办呢?现在可以这样修 ...

  10. iOS---关于UIWebView

    1.加载的web界面可以自由收缩 webView.scalesPageToFit = YES;