从PHP底层源码去深入理解数组,并用C模拟PHP关联数组(原创)
PHP是一门入门容易,使用范围广泛的语言,以其灵活性以及web后端开发被很多人熟知,也被很多人戏称“PHP是世界上最好的语言”。本人是一名“忠实”的PHPer,相信用过PHP的程序员都会体会到PHP数组的灵活性,相对传统的C语言,使用起来很是方便,拥有关联数组(key值可以是字符串),不需要预定义数组空间大小,关联数组,不需要指定key的快速索引赋值等等便利方法,这段时间研究了一下PHP数组的底层结构,并总结分析,里面含有一些我自己的猜想,如有错误请指出。
1.PHP的数组底层结构
哈希结构是一种非常重要的数据结构,他是一种通过key映射到value的结构,由于其特性,可以在大部分的情况下让查找和插入的效率达到O(1),在很多语言或者系统里面都有显性得体现出来,具体的实现思路有很多种。详细的介绍可以看我的博客数据结构之哈希结构
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关联数组(原创)的更多相关文章
- 硬核剖析Java锁底层AQS源码,深入理解底层架构设计
我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...
- Android开发之漫漫长途 Ⅵ——图解Android事件分发机制(深入底层源码)
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
- Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap
声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...
- JDK部分源码阅读与理解
本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/ 不喜欢重复造轮子,不喜欢贴各种东西.JDK ...
- 从源码角度深入理解Toast
Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(", Toast.LENGTH_LONG).show(); 但是我们经常会遇到这 ...
- List-LinkedList、set集合基础增强底层源码分析
List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...
- 从底层源码浅析Mybatis的SqlSessionFactory初始化过程
目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...
- 2018.11.20 Struts2中对结果处理方式分析&struts2内置的方式底层源码剖析
介绍一下struts2内置帮我们封装好的处理结果方式也就是底层源码分析 这是我们的jar包里面找的位置目录 打开往下拉看到result-type节点 name那一列就是我们的type类型取值 上一篇博 ...
- BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享
BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...
随机推荐
- Prime Generator
Peter wants to generate some prime numbers for his cryptosystem. Help him! Your task is to generate ...
- Backbone中的model和collection在做save或者create操作时, 如何选择用POST还是PUT方法 ?
Model和Collection和后台的WEB server进行数据同步非常方便, 都只需要在实行里面添加一url就可以了,backbone会在model进行save或者collection进行cre ...
- Python3.5安装及opencv安装
Python安装注意事项(版本3.5,系统windows)1.安装好Python后将D:\Program Files\Python.D:\Program Files\Python\Scripts加入P ...
- JS 传播事件、取消事件默认行为、阻止事件传播
1.事件处理程序的返回值 通常情况下,返回值false就是告诉浏览器不要执行这个事件相关的默认操作.例如,表单提交按钮的onclick事件处理程序能通过返回false阻止浏览器提交表单,再如a标签的o ...
- 应用.Net+Consul维护RabbitMq的高可用性
懒人学习的过程就是工作中老大让干啥让做啥就研究研究啥,国庆放假回来的周末老大通过钉钉给我布置了个任务, RabbitMQ高可用解决方案,我想说钉钉太坑了: 这是国庆过后9号周日晚上下班给的任务,我周一 ...
- unable to boot the simulator,无法启动模拟器已解决
突然模拟器报错:unable to boot the simulator(无法启动模拟器) 试了好几种解决办法,删除所有的模拟器重启以后再添加,删除钥匙串登陆中的证书,重新安装Xcode都不行 最后通 ...
- WebViewJavascriptBridge源码探究--看OC和JS交互过程
今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFr ...
- android不需要Socket的跨进程推送消息AIDL!
上篇介绍了跨进程实时通讯http://www.cnblogs.com/xiaoxiaing/p/5818161.html 但是他有个缺点就是服务端无法推送消息给客户端,今天这篇文章主要说的就是服务器推 ...
- IOS之Objective-C学习 代理设计模式
鉴于Objective-C是不支持多继承的,所以需要用协议来代替实现其他类的方法,所以有了代理设计模式. 代理,又称委托,delegation. 代理模式可以让一个单继承的类实现父类以外其他类的方法. ...
- 详解java定时任务
在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析JavaTimer定时器. ...