名站导航为爱好php程序的朋友们提供php相关的教程知识。
PhP程序中使用最为频繁的数据类型非字符串和数组莫属,PhP程序比较容易上手也得益于非常灵活的数组类型。 在开始详细介绍这些数据类型之前有必要介绍一下哈希表(hashTable)。 哈希表是PhP程序实现中尤为关键的数据结构。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希表在实践中使用的非常广泛,例如编译器通常会维护的一个符号表来保存标记,很多高级语言中也显式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),删除(Delete)等操作,这些操作在最坏的情况下和链表的性能一样为O(n)。 不过通常并不会这么坏,合理设计的哈希算法能有效的避免这类情况,通常哈希表的这些操作时间复杂度为O(1)。 这也是它被钟爱的原因。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
正是因为哈希表在使用上的便利性及效率上的表现,目前大部分动态语言的实现中都使用了哈希表。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
为了方便读者阅读后面的内容,这里提前列举一下hashTable实现中出现的基本概念。 哈希表是一种通过哈希函数,将特定的键映射到特定值的一种数据结构,它维护键和值之间一一对应关系。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
键(key):用于操作数据的标示,例如PhP程序数组中的索引,或者字符串键等等。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
槽(slot/bucket):哈希表中用于保存数据的一个单元,也就是数据真正存放的容器。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希函数(hash function):将key映射(map)到数据应该存放的slot所在位置的函数。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希冲突(hash collision):哈希函数将两个不同的key映射到同一个索引的情况。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希表可以理解为数组的扩展或者关联数组,数组使用数字下标来寻址,如果关键字(key)的范围较小且是数字的话, 我们可以直接使用数组来完成哈希表,而如果关键字范围太大,如果直接使用数组我们需要为所有可能的key申请空间。 很多情况下这是不现实的。即使空间足够,空间利用率也会很低,这并不理想。同时键也可能并不是数字, 在PhP程序中尤为如此,所以人们使用一种映射函数(哈希函数)来将key映射到特定的域中:631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
h(key) -> index631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
通过合理设计的哈希函数,我们就能将key映射到合适的范围,因为我们的key空间可以很大(例如字符串key), 在映射到一个较小的空间中时可能会出现两个不同的key映射被到同一个index上的情况, 这就是我们所说的出现了冲突。 目前解决hash冲突的方法主要有两种:链接法和开放寻址法。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
冲突解决631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
链接法:链接法通过使用一个链表来保存slot值的方式来解决冲突,也就是当不同的key映射到一个槽中的时候使用链表来保存这些值。 所以使用链接法是在最坏的情况下,也就是所有的key都映射到同一个槽中了,操作链表的时间复杂度为O(n)。 所以选择一个合适的哈希函数是最为关键的。目前PhP程序中hashTable的实现就是采用这种方式来解决冲突的。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
开放寻址法:通常还有另外一种解决冲突的方法:开放寻址法。使用开放寻址法是槽本身直接存放数据, 在插入数据时如果key所映射到的索引已经有数据了,这说明发生了冲突,这是会寻找下一个槽, 如果该槽也被占用了则继续寻找下一个槽,直到寻找到没有被占用的槽,在查找时也使用同样的策律来进行。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希表的实现631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
在了解到哈希表的原理之后要实现一个哈希表也很容易,主要需要完成的工作只有三点:631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
实现哈希函数631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
冲突的解决631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
操作接口的实现631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
首先我们需要一个容器来保存我们的哈希表,哈希表需要保存的内容主要是保存进来的的数据, 同时为了方便的得知哈希表中存储的元素个数,需要保存一个大小字段, 第二个需要的就是保存数据的容器了。作为具体实例操作,下面将实现一个简易的哈希表。基本的数据结构主要有两个, 一个用于保存哈希表本身,另外一个就是用于实际保存数据的单链表了,定义如下:631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
typedef struct _Bucket631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
char *key;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
void *value;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
struct _Bucket *next;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
} Bucket;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
typedef struct _hashTable631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int size;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
Bucket* buckets;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
} hashTable;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
上面的定义和PhP程序中的实现类似,为了便于理解裁剪了大部分无关的细节,在本节中为了简化, key的数据类型为字符串,而存储的数据类型可以为任意类型。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
Bucket结构体是一个单链表,这是为了解决多个key哈希冲突的问题,也就是前面所提到的的链接法。 当多个key映射到同一个index的时候将冲突的元素链接起来。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
哈希函数需要尽可能的将不同的key映射到不同的槽(slot或者bucket)中,首先我们采用一种最为简单的哈希算法实现: 将key字符串的所有字符加起来,然后以结果对哈希表的大小取模,这样索引就能落在数组索引的范围之内了。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
static int hash_str(char *key)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash = 0;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
char *cur = key;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
while(*(cur ) != '\0') {631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
hash = *cur;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
return hash;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// 使用这个宏来求得key在哈希表中的索引631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
#define hASh_INDEX(ht, key) (hash_str((key)) % (ht)->size)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
这个哈希算法比较简单,它的效果并不好,在实际场景下不会使用这种哈希算法, 例如PhP程序中使用的是称为DJBX33A算法, 这里列举了Mysql,OpenSSL等开源软件使用的哈希算法, 有兴趣的读者可以前往参考。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
操作接口的实现631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
为了操作哈希表,实现了如下几个操作函数:631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_init(hashTable *ht); // 初始化哈希表631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_lookup(hashTable *ht, char *key, void **result); // 根据key查找内容631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_insert(hashTable *ht, char *key, void *value); // 将内容插入到哈希表中631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_remove(hashTable *ht, char *key); // 删除key所指向的内容631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_destroy(hashTable *ht);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
下面以插入和获取操作函数为例:631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_insert(hashTable *ht, char *key, void *value)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// check if we need to resize the hashtable631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
resize_hash_table_if_needed(ht); // 哈希表不固定大小,当插入的内容快占满哈表的存储空间631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// 将对哈希表进行扩容, 以便容纳所有的元素631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int index = hASh_INDEX(ht, key); // 找到key所映射到的索引631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
Bucket *org_bucket = ht->buckets[index];631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); // 为新元素申请空间631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
bucket->key = strdup(key);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// 将值内容保存进来, 这里只是简单的将指针指向要存储的内容,而没有将内容复制。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
bucket->value = value; 631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
LOG_MSG("Insert data p: %p\n", value);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
ht->elem_num = 1; // 记录一下现在哈希表中的元素个数631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
if(org_bucket != NULL) { // 发生了碰撞,将新元素放置在链表的头部631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
LOG_MSG("Index collision found with org hashtable: %p\n", org_bucket);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
bucket->next = org_bucket;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
ht->buckets[index]= bucket;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
LOG_MSG("Element inserted at index %i, now we have: %i elements\n",631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
index, ht->elem_num);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
return SUCCESS;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
上面这个哈希表的插入操作比较简单,简单的以key做哈希,找到元素应该存储的位置,并检查该位置是否已经有了内容, 如果发生碰撞则将新元素链接到原有元素链表头部。在查找时也按照同样的策略,找到元素所在的位置,如果存在元素, 则将该链表的所有元素的key和要查找的key依次对比, 直到找到一致的元素,否则说明该值没有匹配的内容。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int hash_lookup(hashTable *ht, char *key, void **result)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
int index = hASh_INDEX(ht, key);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
Bucket *bucket = ht->buckets[index];631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
if(bucket == NULL) return FAILED;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// 查找这个链表以便找到正确的元素,通常这个链表应该是只有一个元素的,也就不用多次631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
// 循环。要保证这一点需要有一个合适的哈希算法,见前面相关哈希函数的链接。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
while(bucket)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
if(strcmp(bucket->key, key) == 0)631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
{631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
LOG_MSG("hashTable found key in index: %i with key: %s value: %p\n",631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
index, key, bucket->value);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
*result = bucket->value; 631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
return SUCCESS;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
bucket = bucket->next;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
LOG_MSG("hashTable lookup missed the key: %s\n", key);631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
return FAILED;631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
}631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
PhP程序中数组是基于哈希表实现的,依次给数组添加元素时,元素之间是有先后顺序的,而这里的哈希表在物理位置上显然是接近平均分布的, 这样是无法根据插入的先后顺序获取到这些元素的,在PhP程序的实现中Bucket结构体还维护了另一个指针字段来维护元素之间的关系。 具体内容在后一小节PhP程序中的hashTable中进行详细说明。上面的例子就是PhP程序中实现的一个精简版。631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
名站网址导航
PhP程序内核探索:理解Zend里的哈希表631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
631AIChat_企业网址导航_网址分类目录_企业黄页网址提交查询专业网站!
好了关于php程序的知识就说到这里希望可以帮助需要的朋友。,