电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

PHP內(nèi)核探索 —— PHP中的哈希表:哈希表是PHP實(shí)現(xiàn)中尤為關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)

瀏覽:4日期:2022-09-16 13:58:36

PHP中使用最為頻繁的數(shù)據(jù)類型非字符串和數(shù)組莫屬,PHP比較容易上手也得益于非常靈活的數(shù)組類型。 在開始詳細(xì)介紹這些數(shù)據(jù)類型之前有必要介紹一下哈希表(HashTable)。 哈希表是PHP實(shí)現(xiàn)中尤為關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)。

哈希表在實(shí)踐中使用的非常廣泛,例如編譯器通常會(huì)維護(hù)的一個(gè)符號(hào)表來保存標(biāo)記,很多高級(jí)語言中也顯式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),刪除(Delete)等操作,這些操作在最壞的情況下和鏈表的性能一樣為O(n)。 不過通常并不會(huì)這么壞,合理設(shè)計(jì)的哈希算法能有效的避免這類情況,通常哈希表的這些操作時(shí)間復(fù)雜度為O(1)。 這也是它被鐘愛的原因。

正是因?yàn)楣1碓谑褂蒙系谋憷约靶噬系谋憩F(xiàn),目前大部分動(dòng)態(tài)語言的實(shí)現(xiàn)中都使用了哈希表。

為了方便讀者閱讀后面的內(nèi)容,這里提前列舉一下HashTable實(shí)現(xiàn)中出現(xiàn)的基本概念。 哈希表是一種通過哈希函數(shù),將特定的鍵映射到特定值的一種數(shù)據(jù)結(jié)構(gòu),它維護(hù)鍵和值之間一一對(duì)應(yīng)關(guān)系。

鍵(key):用于操作數(shù)據(jù)的標(biāo)示,例如PHP數(shù)組中的索引,或者字符串鍵等等。槽(slot/bucket):哈希表中用于保存數(shù)據(jù)的一個(gè)單元,也就是數(shù)據(jù)真正存放的容器。哈希函數(shù)(hash function):將key映射(map)到數(shù)據(jù)應(yīng)該存放的slot所在位置的函數(shù)。哈希沖突(hash collision):哈希函數(shù)將兩個(gè)不同的key映射到同一個(gè)索引的情況。

哈希表可以理解為數(shù)組的擴(kuò)展或者關(guān)聯(lián)數(shù)組,數(shù)組使用數(shù)字下標(biāo)來尋址,如果關(guān)鍵字(key)的范圍較小且是數(shù)字的話, 我們可以直接使用數(shù)組來完成哈希表,而如果關(guān)鍵字范圍太大,如果直接使用數(shù)組我們需要為所有可能的key申請(qǐng)空間。 很多情況下這是不現(xiàn)實(shí)的。即使空間足夠,空間利用率也會(huì)很低,這并不理想。同時(shí)鍵也可能并不是數(shù)字, 在PHP中尤為如此,所以人們使用一種映射函數(shù)(哈希函數(shù))來將key映射到特定的域中:

h(key) -> index

通過合理設(shè)計(jì)的哈希函數(shù),我們就能將key映射到合適的范圍,因?yàn)槲覀兊膋ey空間可以很大(例如字符串key), 在映射到一個(gè)較小的空間中時(shí)可能會(huì)出現(xiàn)兩個(gè)不同的key映射被到同一個(gè)index上的情況, 這就是我們所說的出現(xiàn)了沖突。 目前解決hash沖突的方法主要有兩種:鏈接法和開放尋址法。

沖突解決

鏈接法:鏈接法通過使用一個(gè)鏈表來保存slot值的方式來解決沖突,也就是當(dāng)不同的key映射到一個(gè)槽中的時(shí)候使用鏈表來保存這些值。 所以使用鏈接法是在最壞的情況下,也就是所有的key都映射到同一個(gè)槽中了,操作鏈表的時(shí)間復(fù)雜度為O(n)。 所以選擇一個(gè)合適的哈希函數(shù)是最為關(guān)鍵的。目前PHP中HashTable的實(shí)現(xiàn)就是采用這種方式來解決沖突的。

開放尋址法:通常還有另外一種解決沖突的方法:開放尋址法。使用開放尋址法是槽本身直接存放數(shù)據(jù), 在插入數(shù)據(jù)時(shí)如果key所映射到的索引已經(jīng)有數(shù)據(jù)了,這說明發(fā)生了沖突,這是會(huì)尋找下一個(gè)槽, 如果該槽也被占用了則繼續(xù)尋找下一個(gè)槽,直到尋找到?jīng)]有被占用的槽,在查找時(shí)也使用同樣的策律來進(jìn)行。

哈希表的實(shí)現(xiàn)

在了解到哈希表的原理之后要實(shí)現(xiàn)一個(gè)哈希表也很容易,主要需要完成的工作只有三點(diǎn):

實(shí)現(xiàn)哈希函數(shù)沖突的解決操作接口的實(shí)現(xiàn)

首先我們需要一個(gè)容器來保存我們的哈希表,哈希表需要保存的內(nèi)容主要是保存進(jìn)來的的數(shù)據(jù), 同時(shí)為了方便的得知哈希表中存儲(chǔ)的元素個(gè)數(shù),需要保存一個(gè)大小字段, 第二個(gè)需要的就是保存數(shù)據(jù)的容器了。作為實(shí)例,下面將實(shí)現(xiàn)一個(gè)簡易的哈希表。基本的數(shù)據(jù)結(jié)構(gòu)主要有兩個(gè), 一個(gè)用于保存哈希表本身,另外一個(gè)就是用于實(shí)際保存數(shù)據(jù)的單鏈表了,定義如下:

typedef struct _Bucket{ char *key; void *value; struct _Bucket *next;} Bucket;typedef struct _HashTable{ int size; Bucket* buckets;} HashTable;

上面的定義和PHP中的實(shí)現(xiàn)類似,為了便于理解裁剪了大部分無關(guān)的細(xì)節(jié),在本節(jié)中為了簡化, key的數(shù)據(jù)類型為字符串,而存儲(chǔ)的數(shù)據(jù)類型可以為任意類型。

Bucket結(jié)構(gòu)體是一個(gè)單鏈表,這是為了解決多個(gè)key哈希沖突的問題,也就是前面所提到的的鏈接法。 當(dāng)多個(gè)key映射到同一個(gè)index的時(shí)候?qū)_突的元素鏈接起來。

哈希函數(shù)需要盡可能的將不同的key映射到不同的槽(slot或者bucket)中,首先我們采用一種最為簡單的哈希算法實(shí)現(xiàn): 將key字符串的所有字符加起來,然后以結(jié)果對(duì)哈希表的大小取模,這樣索引就能落在數(shù)組索引的范圍之內(nèi)了。

static int hash_str(char *key){ int hash = 0; char *cur = key; while(*(cur++) != ’0’) {hash += *cur; } return hash;}// 使用這個(gè)宏來求得key在哈希表中的索引#define HASH_INDEX(ht, key) (hash_str((key)) % (ht)->size)

這個(gè)哈希算法比較簡單,它的效果并不好,在實(shí)際場(chǎng)景下不會(huì)使用這種哈希算法, 例如PHP中使用的是稱為DJBX33A算法, 這里列舉了Mysql,OpenSSL等開源軟件使用的哈希算法, 有興趣的讀者可以前往參考。

操作接口的實(shí)現(xiàn)

為了操作哈希表,實(shí)現(xiàn)了如下幾個(gè)操作函數(shù):

int hash_init(HashTable *ht); // 初始化哈希表int hash_lookup(HashTable *ht, char *key, void **result); // 根據(jù)key查找內(nèi)容int hash_insert(HashTable *ht, char *key, void *value); // 將內(nèi)容插入到哈希表中int hash_remove(HashTable *ht, char *key); // 刪除key所指向的內(nèi)容int hash_destroy(HashTable *ht);

下面以插入和獲取操作函數(shù)為例:

int hash_insert(HashTable *ht, char *key, void *value){ // check if we need to resize the hashtable resize_hash_table_if_needed(ht); // 哈希表不固定大小,當(dāng)插入的內(nèi)容快占滿哈表的存儲(chǔ)空間// 將對(duì)哈希表進(jìn)行擴(kuò)容, 以便容納所有的元素 int index = HASH_INDEX(ht, key); // 找到key所映射到的索引 Bucket *org_bucket = ht->buckets[index]; Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); // 為新元素申請(qǐng)空間 bucket->key = strdup(key); // 將值內(nèi)容保存進(jìn)來, 這里只是簡單的將指針指向要存儲(chǔ)的內(nèi)容,而沒有將內(nèi)容復(fù)制。 bucket->value = value; LOG_MSG('Insert data p: %pn', value); ht->elem_num += 1; // 記錄一下現(xiàn)在哈希表中的元素個(gè)數(shù) if(org_bucket != NULL) { // 發(fā)生了碰撞,將新元素放置在鏈表的頭部LOG_MSG('Index collision found with org hashtable: %pn', org_bucket);bucket->next = org_bucket; } ht->buckets[index]= bucket; LOG_MSG('Element inserted at index %i, now we have: %i elementsn',index, ht->elem_num); return SUCCESS;}

上面這個(gè)哈希表的插入操作比較簡單,簡單的以key做哈希,找到元素應(yīng)該存儲(chǔ)的位置,并檢查該位置是否已經(jīng)有了內(nèi)容, 如果發(fā)生碰撞則將新元素鏈接到原有元素鏈表頭部。在查找時(shí)也按照同樣的策略,找到元素所在的位置,如果存在元素, 則將該鏈表的所有元素的key和要查找的key依次對(duì)比, 直到找到一致的元素,否則說明該值沒有匹配的內(nèi)容。

int hash_lookup(HashTable *ht, char *key, void **result){ int index = HASH_INDEX(ht, key); Bucket *bucket = ht->buckets[index]; if(bucket == NULL) return FAILED; // 查找這個(gè)鏈表以便找到正確的元素,通常這個(gè)鏈表應(yīng)該是只有一個(gè)元素的,也就不用多次 // 循環(huán)。要保證這一點(diǎn)需要有一個(gè)合適的哈希算法,見前面相關(guān)哈希函數(shù)的鏈接。 while(bucket) {if(strcmp(bucket->key, key) == 0){ LOG_MSG('HashTable found key in index: %i with key: %s value: %pn',index, key, bucket->value); *result = bucket->value;return SUCCESS;}bucket = bucket->next; } LOG_MSG('HashTable lookup missed the key: %sn', key); return FAILED;}

PHP中數(shù)組是基于哈希表實(shí)現(xiàn)的,依次給數(shù)組添加元素時(shí),元素之間是有先后順序的,而這里的哈希表在物理位置上顯然是接近平均分布的, 這樣是無法根據(jù)插入的先后順序獲取到這些元素的,在PHP的實(shí)現(xiàn)中Bucket結(jié)構(gòu)體還維護(hù)了另一個(gè)指針字段來維護(hù)元素之間的關(guān)系。 具體內(nèi)容在后一小節(jié)PHP中的HashTable中進(jìn)行詳細(xì)說明。上面的例子就是PHP中實(shí)現(xiàn)的一個(gè)精簡版。

標(biāo)簽: PHP
相關(guān)文章:
主站蜘蛛池模板: 山东聚盛新型材料有限公司-纳米防腐隔热彩铝板和纳米防腐隔热板以及钛锡板、PVDF氟膜板供应商 | 巨野电机维修-水泵维修-巨野县飞宇机电维修有限公司 | 喷涂流水线,涂装流水线,喷漆流水线-山东天意设备科技有限公司 | 智能监控-安防监控-监控系统安装-弱电工程公司_成都万全电子 | 新能源汽车电池软连接,铜铝复合膜柔性连接,电力母排-容发智能科技(无锡)有限公司 | 胶水,胶粘剂,AB胶,环氧胶,UV胶水,高温胶,快干胶,密封胶,结构胶,电子胶,厌氧胶,高温胶水,电子胶水-东莞聚力-聚厉胶粘 | 天津云仓-天津仓储物流-天津云仓一件代发-顺东云仓 | 信阳网站建设专家-信阳时代网联-【信阳网站建设百度推广优质服务提供商】信阳网站建设|信阳网络公司|信阳网络营销推广 | 车牌识别道闸_停车场收费系统_人脸识别考勤机_速通门闸机_充电桩厂家_中全清茂官网 | 快速门厂家-快速卷帘门-工业快速门-硬质快速门-西朗门业 | 恒温恒湿试验箱_高低温试验箱_恒温恒湿箱-东莞市高天试验设备有限公司 | 上海乾拓贸易有限公司-日本SMC电磁阀_德国FESTO电磁阀_德国FESTO气缸 | 卫生纸复卷机|抽纸机|卫生纸加工设备|做卫生纸机器|小型卫生纸加工需要什么设备|卫生纸机器设备多少钱一台|许昌恒源纸品机械有限公司 | 杭州货架订做_组合货架公司_货位式货架_贯通式_重型仓储_工厂货架_货架销售厂家_杭州永诚货架有限公司 | 依维柯自动挡房车,自行式国产改装房车,小型房车价格,中国十大房车品牌_南京拓锐斯特房车 - 南京拓锐斯特房车 | SOUNDWELL 编码器|电位器|旋转编码器|可调电位器|编码开关厂家-广东升威电子制品有限公司 | 成都顶呱呱信息技术有限公司-贷款_个人贷款_银行贷款在线申请 - 成都贷款公司 | 东莞动力锂电池保护板_BMS智能软件保护板_锂电池主动均衡保护板-东莞市倡芯电子科技有限公司 | Brotu | 关注AI,Web3.0,VR/AR,GPT,元宇宙区块链数字产业 | 高压负荷开关-苏州雷尔沃电器有限公司| 德国UST优斯特氢气检漏仪-德国舒赐乙烷检测仪-北京泽钏 | 山东太阳能路灯厂家-庭院灯生产厂家-济南晟启灯饰有限公司 | 镀锌角钢_槽钢_扁钢_圆钢_方矩管厂家_镀锌花纹板-海邦钢铁(天津)有限公司 | 中细软知识产权_专业知识产权解决方案提供商 | 全自动五线打端沾锡机,全自动裁线剥皮双头沾锡机,全自动尼龙扎带机-东莞市海文能机械设备有限公司 | 粉末冶金-粉末冶金齿轮-粉末冶金零件厂家-东莞市正朗精密金属零件有限公司 | 济南玻璃安装_济南玻璃门_济南感应门_济南玻璃隔断_济南玻璃门维修_济南镜片安装_济南肯德基门_济南高隔间-济南凯轩鹏宇玻璃有限公司 | 艺术涂料_进口艺术涂料_艺术涂料加盟_艺术涂料十大品牌 -英国蒙太奇艺术涂料 | 板框压滤机-隔膜压滤机-厢式压滤机生产厂家-禹州市君工机械设备有限公司 | 板式换网器_柱式换网器_自动换网器-郑州海科熔体泵有限公司 | 塑胶跑道施工-硅pu篮球场施工-塑胶网球场建造-丙烯酸球场材料厂家-奥茵 | 合肥抖音SEO网站优化-网站建设-网络推广营销公司-百度爱采购-安徽企匠科技 | 定制异形重型钢格栅板/钢格板_定做踏步板/排水沟盖板_钢格栅板批发厂家-河北圣墨金属制品有限公司 | 酸度计_PH计_特斯拉计-西安云仪| 贵州自考_贵州自学考试网 | 电销卡 防封电销卡 不封号电销卡 电话销售卡 白名单电销卡 电销系统 外呼系统 | 气动隔膜泵-电动隔膜泵-循环热水泵-液下排污/螺杆/管道/化工泵「厂家」浙江绿邦 | 锡膏喷印机-全自动涂覆机厂家-全自动点胶机-视觉点胶机-深圳市博明智控科技有限公司 | 【MBA备考网】-2024年工商管理硕士MBA院校/报考条件/培训/考试科目/提前面试/考试/学费-MBA备考网 | 合肥办公室装修 - 合肥工装公司 - 天思装饰 | 天然气分析仪-液化气二甲醚分析仪|传昊仪器 |