php核心解析:PHP中的雜湊表_PHP教程

來源:互聯網
上載者:User
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依次對比, 直到找到一致的元素,否則說明該值沒有匹配的內容。

複製代碼 代碼如下:
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;

// 尋找這個鏈表以便找到正確的元素,通常這個鏈表應該是只有一個元素的,也就不用多次
// 迴圈。要保證這一點需要有一個合適的雜湊演算法,見前面相關雜湊函數的連結。
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中數組是基於雜湊表實現的,依次給數組添加元素時,元素之間是有先後順序的,而這裡的雜湊表在物理位置上顯然是接近平均分布的, 這樣是無法根據插入的先後順序擷取到這些元素的,在PHP的實現中Bucket結構體還維護了另一個指標欄位來維護元素之間的關係。 具體內容在後一小節PHP中的HashTable中進行詳細說明。上面的例子就是PHP中實現的一個精簡版。

http://www.bkjia.com/PHPjc/728096.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/728096.htmlTechArticlePHP中使用最為頻繁的資料類型非字串和數組莫屬,PHP比較容易上手也得益於非常靈活的數群組類型。 在開始詳細介紹這些資料類型之前有必...

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.