欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

php內(nèi)核解析:PHP中的哈希表

 更新時間:2014年01月30日 23:02:19   作者:  
PHP中使用最為頻繁的數(shù)據(jù)類型非字符串和數(shù)組莫屬,PHP比較容易上手也得益于非常靈活的數(shù)組類型。 在開始詳細介紹這些數(shù)據(jù)類型之前有必要介紹一下哈希表(HashTable)。 哈希表是PHP實現(xiàn)中尤為關鍵的數(shù)據(jù)結構

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

哈希表在實踐中使用的非常廣泛,例如編譯器通常會維護的一個符號表來保存標記,很多高級語言中也顯式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),刪除(Delete)等操作,這些操作在最壞的情況下和鏈表的性能一樣為O(n)。 不過通常并不會這么壞,合理設計的哈希算法能有效的避免這類情況,通常哈希表的這些操作時間復雜度為O(1)。 這也是它被鐘愛的原因。
正是因為哈希表在使用上的便利性及效率上的表現(xiàn),目前大部分動態(tài)語言的實現(xiàn)中都使用了哈希表。

為了方便讀者閱讀后面的內(nèi)容,這里提前列舉一下HashTable實現(xiàn)中出現(xiàn)的基本概念。 哈希表是一種通過哈希函數(shù),將特定的鍵映射到特定值的一種數(shù)據(jù)結構,它維護鍵和值之間一一對應關系。
鍵(key):用于操作數(shù)據(jù)的標示,例如PHP數(shù)組中的索引,或者字符串鍵等等。

槽(slot/bucket):哈希表中用于保存數(shù)據(jù)的一個單元,也就是數(shù)據(jù)真正存放的容器。

哈希函數(shù)(hash function):將key映射(map)到數(shù)據(jù)應該存放的slot所在位置的函數(shù)。

哈希沖突(hash collision):哈希函數(shù)將兩個不同的key映射到同一個索引的情況。

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

復制代碼 代碼如下:

h(key) -> index

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

沖突解決

鏈接法:鏈接法通過使用一個鏈表來保存slot值的方式來解決沖突,也就是當不同的key映射到一個槽中的時候使用鏈表來保存這些值。 所以使用鏈接法是在最壞的情況下,也就是所有的key都映射到同一個槽中了,操作鏈表的時間復雜度為O(n)。 所以選擇一個合適的哈希函數(shù)是最為關鍵的。目前PHP中HashTable的實現(xiàn)就是采用這種方式來解決沖突的。
開放尋址法:通常還有另外一種解決沖突的方法:開放尋址法。使用開放尋址法是槽本身直接存放數(shù)據(jù), 在插入數(shù)據(jù)時如果key所映射到的索引已經(jīng)有數(shù)據(jù)了,這說明發(fā)生了沖突,這是會尋找下一個槽, 如果該槽也被占用了則繼續(xù)尋找下一個槽,直到尋找到?jīng)]有被占用的槽,在查找時也使用同樣的策律來進行。

哈希表的實現(xiàn)

在了解到哈希表的原理之后要實現(xiàn)一個哈希表也很容易,主要需要完成的工作只有三點:
實現(xiàn)哈希函數(shù)
沖突的解決
操作接口的實現(xiàn)
首先我們需要一個容器來保存我們的哈希表,哈希表需要保存的內(nèi)容主要是保存進來的的數(shù)據(jù), 同時為了方便的得知哈希表中存儲的元素個數(shù),需要保存一個大小字段, 第二個需要的就是保存數(shù)據(jù)的容器了。作為實例,下面將實現(xiàn)一個簡易的哈希表?;镜臄?shù)據(jù)結構主要有兩個, 一個用于保存哈希表本身,另外一個就是用于實際保存數(shù)據(jù)的單鏈表了,定義如下:

復制代碼 代碼如下:

typedef struct _Bucket
{
 char *key;
 void *value;
 struct _Bucket *next;

} Bucket;

typedef struct _HashTable
{
 int size;
 Bucket* buckets;
} HashTable;

上面的定義和PHP中的實現(xiàn)類似,為了便于理解裁剪了大部分無關的細節(jié),在本節(jié)中為了簡化, key的數(shù)據(jù)類型為字符串,而存儲的數(shù)據(jù)類型可以為任意類型。
Bucket結構體是一個單鏈表,這是為了解決多個key哈希沖突的問題,也就是前面所提到的的鏈接法。 當多個key映射到同一個index的時候將沖突的元素鏈接起來。
哈希函數(shù)需要盡可能的將不同的key映射到不同的槽(slot或者bucket)中,首先我們采用一種最為簡單的哈希算法實現(xiàn): 將key字符串的所有字符加起來,然后以結果對哈希表的大小取模,這樣索引就能落在數(shù)組索引的范圍之內(nèi)了。

復制代碼 代碼如下:

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等開源軟件使用的哈希算法, 有興趣的讀者可以前往參考。
操作接口的實現(xiàn)
為了操作哈希表,實現(xiàn)了如下幾個操作函數(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); // 哈希表不固定大小,當插入的內(nèi)容快占滿哈表的存儲空間
 // 將對哈希表進行擴容, 以便容納所有的元素

int index = HASH_INDEX(ht, key); // 找到key所映射到的索引

Bucket *org_bucket = ht->buckets[index];
 Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); // 為新元素申請空間

bucket->key = strdup(key);
 // 將值內(nèi)容保存進來, 這里只是簡單的將指針指向要存儲的內(nèi)容,而沒有將內(nèi)容復制。
 bucket->value = value;

LOG_MSG("Insert data p: %p\n", value);

ht->elem_num += 1; // 記錄一下現(xiàn)在哈希表中的元素個數(shù)

if(org_bucket != NULL) { // 發(fā)生了碰撞,將新元素放置在鏈表的頭部
 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做哈希,找到元素應該存儲的位置,并檢查該位置是否已經(jīng)有了內(nèi)容, 如果發(fā)生碰撞則將新元素鏈接到原有元素鏈表頭部。在查找時也按照同樣的策略,找到元素所在的位置,如果存在元素, 則將該鏈表的所有元素的key和要查找的key依次對比, 直到找到一致的元素,否則說明該值沒有匹配的內(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;

 // 查找這個鏈表以便找到正確的元素,通常這個鏈表應該是只有一個元素的,也就不用多次
 // 循環(huán)。要保證這一點需要有一個合適的哈希算法,見前面相關哈希函數(shù)的鏈接。
 while(bucket)
 {
 if(strcmp(bucket->key, key) == 0)
 {
 LOG_MSG("HashTable found key in index: %i with key: %s value: %p\n",
 index, key, bucket->value);
 *result = bucket->value;
 return SUCCESS;
 }

 bucket = bucket->next;
 }

 LOG_MSG("HashTable lookup missed the key: %s\n", key);
 return FAILED;
}

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

相關文章

  • 詳解php反序列化之字符逃逸法

    詳解php反序列化之字符逃逸法

    這篇文章主要為大家詳細介紹了php反序列化之字符逃逸法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • php 操作數(shù)組(合并,拆分,追加,查找,刪除等)

    php 操作數(shù)組(合并,拆分,追加,查找,刪除等)

    這篇文章主要介紹了php自帶的一些操作數(shù)組的函數(shù),特整理下方便大家使用
    2012-07-07
  • PHP5.5迭代生成器用法實例詳解

    PHP5.5迭代生成器用法實例詳解

    這篇文章主要介紹了PHP5.5迭代生成器用法,結合實例形式詳細分析了PHP5.5迭代生成器的功能,定義及相關使用技巧,需要的朋友可以參考下
    2016-03-03
  • php實現(xiàn)隨機顯示圖片方法匯總

    php實現(xiàn)隨機顯示圖片方法匯總

    本文分享一個php實現(xiàn)的隨機顯示圖片的函數(shù),可以將指定文件夾中存放的圖片隨機地顯示出來。有興趣的朋友研究下吧。
    2015-05-05
  • php getsiteurl()函數(shù)

    php getsiteurl()函數(shù)

    理解:從字面上看,是獲得站點的URL
    2009-09-09
  • 用C/C++擴展你的PHP 為你的php增加功能

    用C/C++擴展你的PHP 為你的php增加功能

    PHP取得成功的一個主要原因之一是她擁有大量的可用擴展。web開發(fā)者無論有何種需求,這種需求最有可能在PHP發(fā)行包里找到。PHP發(fā)行包包括支持各種數(shù)據(jù)庫,圖形文件格式,壓縮,XML技術擴展在內(nèi)的許多擴展
    2012-09-09
  • PHP+iframe模擬Ajax上傳文件功能示例

    PHP+iframe模擬Ajax上傳文件功能示例

    這篇文章主要介紹了PHP+iframe模擬Ajax上傳文件功能,結合實例形式分析了iframe模擬Ajax上傳文件與后臺php接收處理相關操作技巧,需要的朋友可以參考下
    2019-07-07
  • PHP中通過語義URL防止網(wǎng)站被攻擊的方法分享

    PHP中通過語義URL防止網(wǎng)站被攻擊的方法分享

    好奇心是很多攻擊者的主要動機,語義URL 攻擊就是一個很好的例子。此類攻擊主要包括對URL 進行編輯以期發(fā)現(xiàn)一些有趣的事情。
    2011-09-09
  • php獲取百度收錄、百度熱詞及百度快照的方法

    php獲取百度收錄、百度熱詞及百度快照的方法

    這篇文章主要介紹了php獲取百度收錄、百度熱詞及百度快照的方法,實例分析了php抓取百度頁面及對應字符串分析的技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • PHP解析RSS的方法

    PHP解析RSS的方法

    這篇文章主要介紹了PHP解析RSS的方法,實例分析了php解析RSS的原理與XML文件的操作技巧,需要的朋友可以參考下
    2015-03-03

最新評論