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

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

iBatis和MyBatis:區別和優勢解析導語:在Java開發中,持久化是一個常見的需求,而iBatis和MyBatis是兩個廣泛使用的持久化框架。雖然它們有很多相似之處,但也有一些關鍵的區別和優勢。本文將透過詳細分析這兩個框架的特性、用法和範例程式碼,為讀者提供更全面的了解。一、iBatis特性:iBatis是目前較老舊的持久化框架,它使用SQL映射文件

深入解析HTTP狀態碼460的作用和應用場景HTTP狀態碼是Web開發中非常重要的一部分,用來表示客戶端和伺服器之間的通訊狀態。其中,HTTP狀態碼460是較為特殊的狀態碼,本文將深入解析它的作用與應用場景。 HTTP狀態碼460的定義HTTP狀態碼460的具體定義是"ClientClosedRequest",意為客戶端關閉請求。此狀態碼主要用於表示

Oracle錯誤3114詳解:如何快速解決,需要具體程式碼範例在Oracle資料庫開發與管理過程中,我們常常會遇到各種各樣的錯誤,其中錯誤3114是比較常見的一個問題。錯誤3114通常表示資料庫連線出現問題,可能是網路故障、資料庫服務停止、或連接字串設定不正確等原因導致的。本文將詳細解釋錯誤3114的產生原因,以及如何快速解決這個問題,並附上具體的程式碼

在Ubuntu22.04上安裝Linux核心可以按照以下步驟進行操作:更新系統:首先,確保你的Ubuntu系統是最新的,執行以下命令更新系統軟體包:sudoaptupdatesudoaptupgrade下載核心檔案:訪問Linux核心官方網站()下載所需的核心版本。選擇一個穩定版本並下載原始碼檔案(以.tar.gz或.tar.xz為副檔名),例如:wget解壓縮檔:使用下列指令解壓縮下載的核心原始碼檔案:tar-xflinux-5.14.tar. xz安裝建置依賴:安裝建置核心所需的工具和相依性。執

Wormhole在區塊鏈互通性方面處於領先地位,專注於創建有彈性、面向未來的去中心化系統,優先考慮所有權、控制權和無需許可的創新。這個願景的基礎是對技術專業知識、道德原則和社群一致性的承諾,旨在以簡單、清晰和廣泛的多鏈解決方案套件重新定義互通性格局。隨著零知識證明、擴容方案和功能豐富的Token標準的興起,區塊鏈變得更加強大,而互通性也變得越來越重要。在這個不斷創新的應用程式環境中,新穎的治理系統和實用功能為整個網路的資產帶來了前所未有的機會。協議建構者現在正在努力思考如何在這個新興的多鏈

Linux修改核心(kernel)啟動順序一、RHEL6/CentOS6修改核心啟動順序檢視/etc/grub.conf檔案以決定係統核心狀況。根據檔案顯示,系統有兩個核心版本,分別為2.6.32-573.18.1.el6.x86_64和2.6.32-431.23.3.el6.x86_64。核心版本從上到下列出。在grub.conf檔案中,可以透過調整default參數來決定係統啟動時使用哪個核心版本。預設值為0,表示系統將啟動最新的核心版本。值為0對應grub.conf檔案中列出的第一個內

【PHP中點的意義和用法解析】在PHP中,中點(.)是常用的運算符,用來連接兩個字串或物件的屬性或方法。在本文中,我們將深入探討PHP中點的意義和用法,並透過具體的程式碼範例加以說明。 1.連接字串中點運算子.在PHP中最常見的用法是連接兩個字串。透過將.放置在兩個字串之間,可以將它們拼接在一起,形成一個新的字串。 $string1=&qu

由於篇幅限制,以下是一個簡短的文章:Apache2是常用的Web伺服器軟體,而PHP是廣泛使用的伺服器端腳本語言。在建置網站過程中,有時會遇到Apache2無法正確解析PHP檔案的問題,導致PHP程式碼無法執行。這種問題通常是因為Apache2沒有正確配置PHP模組,或是PHP模組與Apache2的版本不相容所導致的。解決這個問題的方法一般有兩種,一種是
