php数组的数据结构

从zvalue_value结构中看出,php的数组是用哈希表来实现的:HashTable *ht;

引用网上的内容介绍下哈希表:

哈希表是一种通过哈希函数,将特定的键映射到特定值的一种数据结构,它维护键和值之间一一对应关系。

键(key):用于操作数据的标示,例如PHP数组中的索引,或者字符串键等等。

槽(slot/bucket):哈希表中用于保存数据的一个单元,也就是数据真正存放的容器。

哈希函数(hash function):将key映射(map)到数据应该存放的slot所在位置的函数。

哈希冲突(hash collision):哈希函数将两个不同的key映射到同一个索引的情况。

当出现冲突时,php使用链接法来解决冲突,也就是不同的key映射到同一个槽时,这些key将按链表的方式存储,具体解决方法在下文中介绍。

php哈希的实现

php中和哈希实现相关的两个文件是Zend/zend_hash.h和Zend/zend_hash.c。在zend_hash.h中申明了哈希的数据结构和相关操作的函数,包括初始化/释放、哈希函数、基本增删改查操作函数以及为方便操作而提供的类似于utility性质的实用函数。

哈希函数hash function

php采用经典的Daniel J. Bernstein的'times 33'哈希算法,这个算法是已知的最好的哈希算法之一,在处理以字符串为键值的哈希时,有着极快的计算效率和很好哈希分布,具体算法可参考相关文档。

Bucket数据结构

php采用的是链接法解决冲突,所以每个Bucket数据结构中除了指向实际数据的指针,还需要指向链表下一个元素的指针,为了加快查找时间,php的bucket实现中采用双向链表,具体结构如下:

typedef struct bucket {

ulong h; // 对char *key进行hash后的值,或者是数字的索引值

uint nKeyLength; // hash关键字长度,如果索引为数字,那么这个值为0

void *pData; // 指向用户数据(其实是数据副本,在做UPDATE_DATA或者INIT_DATA时分配了新的空间,将用户数据拷贝了一份),如果是一个指针,那么指向下面的pDataPtr

void *pDataPtr; // 如果是指针数据,那么这个值指向真正的用户数据(同上一个,也是用户数据的副本),同时上面的pData指向该值。

struct bucket *pListNext; // 指向整个hash表的下一个元素

struct bucket *pListLast; // 指向整个hash表的上一个元素

struct bucket *pNext; // 指向同一个Bucket内的下一个元素

struct bucket *pLast; // 指向同一个Bucket内的上一个元素

char arKey[1];

} Bucket;

这个结构中有两套双向链表指针:pListNext&pListLast,pNext&pLast。其中,pListNext和pListLast用于构成整个哈希表的链表,这个链表中包括哈希表中的所有元素,按插入顺序构成链表。而pNext和pLast则构成同一个槽位内的Bucket链表,用于解决冲突。

拿一个具体的例子来看php哈希的内部指针,如图中所示(该图来自于网络),先后插入Bucket1,Bucket2和Bucket3,同时Bucket1和Bucket2冲突:

php的哈希(实现数组的数据结构) - 风信子 - 风信子

这种结构中的几个关键点:

1. 初始化时哈希表的大小:在初始化函数_zend_hash_init中,哈希表的实际大小被初始化为2的整数倍,这样做的好处是,哈希表的可用索引值为0 - (2^n-1),当需要计算某个哈希值在哈希表中的具体位置时,直接用与方法:nIndex = h & ht->nTableMask;其中,nTableMash = nTableSize-1;按位与操作相比取模操作快很多。

2. 遍历哈希表:在HashTable数据结构中,有这么一个成员:pInternalPointer。这个变量指向当前遍历的元素,所以可以快速的取到下一个元素(通过pListNext取得整个哈希表中的下一个,pListLast取得整个哈希表中的上一个)。所以执行foreach语句比执行for语句快。

3. 纯数字字符串key的索引:如果key是一个数值,那么php不会去计算这个key的哈希值,直接拿这个数值作为哈希值(如果是一个负数,会转成ulong),如果key是一个纯数字组成的字符串,php的实际做法是调用ZEND_HANDLE_NUMERIC宏,在这个宏中,会将字符串转成数值,然后将这个数值作为哈希值,所以对于纯数字的字符串key和相应数值的key没有区别,比如'10'和10这两个key是一样的。

4. 一个新的Bucket插入到哈希表中的两个操作,一个是插入到某个槽位上的List:CONNECT_TO_BUCKET_DLLIST宏,另一个是插入到哈希表的整个链表中:CONNET_TO_GLOBAL_DLLIST宏。

5. resize操作:在宏ZEND_HASH_IF_FULL_DO_RESIZE中检查是否需要resize,当元素个数超过哈希表的大小时,就做resize操作。resize时,直接将nTableSize左移1位,然后将已有的元素重新hash。

6. 实用函数:php的哈希中提供了很多实用的函数,包括copy,merge,sort(如果指定了renumbre参数,那么key会重建),minmax,方便遍历的forward和backward等。


另外:hash解决冲突的几种算法:

1)线性探查法(Linear Probing)
该方法的基本思想是:
    将散列表T[0..m-1]看成是一个循环向量,若初始探查的地址为d(即h(key)=d),则最长的探查序列为:
        d,d+l,d+2,…,m-1,0,1,…,d-1
     即:探查时从地址d开始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到T[d-1]为止。
探查过程终止于三种情况:
     (1)若当前探查的单元为空,则表示查找失败(若是插入则将key写入其中);
    (2)若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;
     (3)若探查到T[d-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。
利用开放地址法的一般形式,线性探查法的探查序列为:
        hi=(h(key)+i)%m 0≤i≤m-1 //即di=i

(2)线性补偿探测法 
线性补偿探测法的基本思想是:
将线性探测的步长从 1 改为 Q ,即将上述算法中的 j = (j + 1) % m 改为: j = (j + Q) % m ,而且要求 Q 与 m 是互质的,以便能探测到哈希表中的所有单元。
【例】 PDP-11 小型计算机中的汇编程序所用的符合表,就采用此方法来解决冲突,所用表长 m = 1321 ,选用 Q = 25 。

(3)随机探测 
随机探测的基本思想是:
将线性探测的步长从常数改为随机数,即令: j = (j + RN) % m ,其中 RN 是一个随机数。在实际程序中应预先用随机数发生器产生一个随机序列,将此序列作为依次探测的步长。这样就能使不同的关键字具有不同的探测次序,从而可以避 免或减少堆聚。基于与线性探测法相同的理由,在线性补偿探测法和随机探测法中,删除一个记录后也要打上删除标记。

(4)拉链法解决冲突的方法
     拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。
【例】设有 m = 5 , H(K) = K mod 5 ,关键字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外链地址法所建立的哈希表如下图所示:


          


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PHP中,常用的数据结构和算法如下: 数据结构: 1. 数组(Array):一种有序的数据集合,可以通过索引或关联键访问元素。 2. 链表(Linked List):由节点组成的数据结构,每个节点存储数据和指向下一个节点的指针。 3. 栈(Stack):一种后进先出(LIFO)的数据结构,只允许在栈顶进行插入和删除操作。 4. 队列(Queue):一种先进先出(FIFO)的数据结构,允许在队尾进行插入操作,在队头进行删除操作。 5. 哈希表(Hash Table):根据关键字直接访问内存中存储的值,通过哈希函数将关键字映射到数组索引。 算法: 1. 排序算法:如冒泡排序、选择排序、插入排序、快速排序、归并排序等。 2. 搜索算法:如线性搜索、二分搜索等。 3. 图算法:如深度优先搜索(DFS)、广度优先搜索(BFS)、最短路径算法(Dijkstra算法、Floyd-Warshall算法)、最小生成树算法(Prim算法、Kruskal算法)等。 4. 动态规划(Dynamic Programming):通过将问题分解为子问题,并保存子问题的解来解决复杂问题。 5. 贪心算法(Greedy Algorithm):每一步选择当前状态下最优的解,以期望达到全局最优解。 6. 回溯算法(Backtracking):通过尝试所有可能的解,并逐步构建可行解的方式来求解问题。 这些数据结构和算法在PHP开发中被广泛应用,用于解决各种问题并提高程序的效率和性能。你可以使用PHP内置的数据结构和算法实现,或者使用第三方库和组件来简化开发过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值