手机版

深入服务器端编程语言(专业超文本预处理器的缩写)中的散列表结构详解

时间:2021-11-19 来源:互联网 编辑:宝哥软件园 浏览:

散列表是阿维斯陀经注解引擎中最重要、使用最广泛的数据结构,它被用来存储几乎所有的东西。1.2.1 数据结构散列表数据结构定义如下:复制代码代码如下:typedef结构桶ulong h;//存放hashuint nKeyLengthvoid * pData//指向价值,是用户数据的副本void * pDataPtrstruct存储桶* pListNext//pListNext和pListLast组成结构桶* pListLast//整个散列表的双链表struct bucket * pNext//pNext和塑料用于组成某个混杂对应struct bucket * PlST//的双链表char ArKey[1];//key } Bucket;typedef struct _ hashtable { uint nTableSize;uint问题询问;uint nNumOfElementsulong nNextFreeElementBucket * pInternalPointer/*用于元素遍历*/Bucket * pli thead;桶* pListTail水桶* *阿不思;//哈希数组dtor _ func _ t pDestructor//哈希表初始化时指定,销毁水桶时调用zend_bool持久;//是否采用C的内存分配例程无符号字符napplycountZEND _ bool Bapplypprotection # if ZEND _ debuggint不一致;#endif}哈希表;总的来说,Zend的散列表是一种链表散列,同时也为线性遍历进行了优化,图示如下

哈希表包含两个数据结构,一个链表哈希和一个双向链表。前者用于快速键值查询,后者便于线性遍历和排序。存储桶存在于两种数据结构中。关于这种数据结构的一些解释:为什么在链表的散列中使用双链表?链表的通用哈希只需要通过按键操作,只有一个链表就足够了。然而,Zend有时需要从链表的散列中删除给定的Bucket,这可以通过使用双链表非常有效地实现。问题询问是做什么的?该值用于将哈希值转换为数组下标。初始化HashTable时,Zend首先为arBuckets数组分配nTableSize的内存,nTableSize不小于用户指定大小的最小2 n,即二进制的10*。Ntablemask=Ntablemask1,即二进制01*。此时,Ntablemask正好落在[0,Ntablemask1]中,Zend使用它作为索引来访问arBuckets数组。pDataPtr是做什么的?通常,当用户插入键值对时,Zend会复制值并将pData指向值的副本。复制操作需要调用Zend内部例程emalloc来分配内存,这是一个非常耗时的操作,并且会消耗一块大于值的内存(多余的内存用于存储cookie),如果值很小,就会造成很大的浪费。考虑到HashTable主要用于存储指针值,Zend引入了pDataPtr。当值和指针一样长时,Zend直接复制到pDataPtr中,并将pData指向pDataPtr。这避免了emalloc操作,也有利于提高Cache命中率。为什么arKey只有1码?为什么不用指针来管理键呢?ArKey是一个存储按键的数组,但是它的大小只有1,不足以放下按键。在HashTable的初始化函数中可以找到以下代码:复制代码如下: p=(bucket *)PE malloc(sizeof(bucket)-1 key length,ht-persistent);可以看到,Zend分配了一个有足够内存的Bucket,可以放下自己和key。上半部分是Bucket,下半部分是Key,arKey“恰好”是Bucket的最后一个元素,所以arKey可以用来访问key。这种技术在内存管理例程中最为常见。分配内存时,它实际分配的内存大于指定的大小。额外的上部通常被称为cookie,它存储这个内存的信息,如块大小、上一个指针、下一个指针等。百度的Transmit程序使用了这种方法。不使用指针来管理键的目的是减少一次电子分配操作并提高缓存命中率。另一个必要的原因是,大多数情况下密钥是固定的,整个Bucket不会因为密钥变长而重新分配。同时,它还解释了为什么值没有作为——的数组分配,因为值是可变的。1.2.2 PHP数组关于HashTable还有一个问题没有回答,那就是nNextFreeElement是做什么的?与普通哈希值不同,Zend的HashTable允许用户直接指定哈希值,而忽略甚至不指定key(此时nKeyLength为0)。同时,HashTable还支持追加操作,用户只需要提供值,而不需要指定hash值。此时,Zend使用nNextFreeElement作为哈希,然后递增nNextFreeElement。哈希表的行为似乎很奇怪,因为它将无法通过键访问值,而且它根本不是哈希。理解问题的关键在于,PHP数组是由HashTable实现的——关联数组,它使用正常的k-v映射向HashTable添加元素,其关键是用户指定的字符串。非关联数组直接使用数组下标作为哈希值,没有关键字;在数组中使用关联和非关联时,或者使用array_push操作时,需要使用nNextFreeElement。再来看value,PHP数组的值直接使用了zval的一般结构,pData指向zval*,会像上一节描述的那样直接存储在pDataPtr中。由于直接使用zval,数组的元素可以是任何PHP类型。

数组的遍历操作,即foreach、each等。是通过HashTable的双链表实现的,pInternalPointer将当前位置记录为光标。1.2.3变量符号表除了数组之外,HashTable还用来存储很多其他数据,比如PHP函数、变量符号、加载的模块、类成员等。一个变量符号表相当于一个关联数组,它的键是变量名(很明显使用长的变量名不是一个好主意),值是zval*。任何时候,PHP代码都可以看到两个变量符号表——symbol_table和active_symbol_table——。前者用于存储全局变量,称为全局符号表。后者是指向当前活动的变量符号表的指针,变量符号表通常是全局符号表。但是,每次输入一个PHP函数(在这种情况下,是用户使用PHP代码创建的函数),Zend都会在函数的本地部分创建一个变量符号表,并将active_symbol_table指向本地符号表。Zend总是使用active_symbol_table来访问变量,从而实现局部变量的范围控制。但是,如果标记为全局的变量在函数中被本地访问,Zend将进行特殊处理。——在active_symbol_table中创建对symbol_table中同名变量的引用,如果symbol_table中没有同名变量,将首先创建。1.3内存和文件程序拥有的资源一般包括内存和文件。对于普通程序,这些资源是面向过程的。当这个过程完成后,操作系统或者C库会自动回收那些我们没有明确释放的资源。但是,PHP程序有其特殊性。它基于页面。当一个页面运行时,它还会申请内存或文件等资源。但是,页面运行后,操作系统或C库可能不知道它需要回收资源。例如,我们将php作为一个模块编译到apache中,并在prefork或worker模式下运行apache。在这种情况下,apache进程或线程被重用,php页面分配的内存将永远留在内存中,直到它离开内核。针对这个问题,Zend提供了一套内存分配API,与C语言中对应的函数相同,只是这些函数从Zend自己的内存池中分配内存,可以实现基于页面的自动回收。在我们的模块中,为页面分配的内存应该使用这些API,而不是C例程,否则Zend会在页面结束时尝试释放我们的内存,结果通常是崩溃。e malloc()efree()e str dup()e calloc()e realloc()此外,Zend还以VCWD_xxx的形式提供了一组宏来替代操作系统的c库和相应的文件API。这些宏可以支持PHP的虚拟工作目录,应该始终在模块代码中使用。宏的具体定义见PHP源代码“TSRM/tsrm_virtual_cwd.h”。你可能会注意到那些宏都没有提供close操作,因为close的对象是一个打开的资源,不涉及文件路径,所以可以直接使用C或者操作系统例程;类似地,读/写等操作是直接使用C或操作系统的例程。

版权声明:深入服务器端编程语言(专业超文本预处理器的缩写)中的散列表结构详解是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。