日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

PHP内核中的哈希表结构

發布時間:2023/11/27 生活经验 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PHP内核中的哈希表结构 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

https://github.com/HonestQiao/tipi/commit/17ca680289e490763a6a402f79afa2a13802bb36

下載:https://github.com/HonestQiao/tipi/tree/master/book/sample/chapt03

原文地址:http://www.nowamagic.net/librarys/veda/detail/1344

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所映射到的索引已經有數據了,這說明發生了沖突,這是會尋找下一個槽, 如果該槽也被占用了則繼續尋找下一個槽,直到尋找到沒有被占用的槽,在查找時也使用同樣的策律來進行。

哈希表的實現

在了解到哈希表的原理之后要實現一個哈希表也很容易,主要需要完成的工作只有三點:

  1. 實現哈希函數
  2. 沖突的解決
  3. 操作接口的實現

首先我們需要一個容器來保存我們的哈希表,哈希表需要保存的內容主要是保存進來的的數據, 同時為了方便的得知哈希表中存儲的元素個數,需要保存一個大小字段, 第二個需要的就是保存數據的容器了。作為實例,下面將實現一個簡易的哈希表。基本的數據結構主要有兩個, 一個用于保存哈希表本身,另外一個就是用于實際保存數據的單鏈表了,定義如下:

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 hashtableresize_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中實現的一個精簡版。

延伸閱讀

此文章所在專題列表如下:

  1. PHP內核探索:從SAPI接口開始
  2. PHP內核探索:一次請求的開始與結束
  3. PHP內核探索:一次請求生命周期
  4. PHP內核探索:單進程SAPI生命周期
  5. PHP內核探索:多進程/線程的SAPI生命周期
  6. PHP內核探索:Zend引擎
  7. PHP內核探索:再次探討SAPI
  8. PHP內核探索:Apache模塊介紹
  9. PHP內核探索:通過mod_php5支持PHP
  10. PHP內核探索:Apache運行與鉤子函數
  11. PHP內核探索:嵌入式PHP
  12. PHP內核探索:PHP的FastCGI
  13. PHP內核探索:如何執行PHP腳本
  14. PHP內核探索:PHP腳本的執行細節
  15. PHP內核探索:操作碼OpCode
  16. PHP內核探索:PHP里的opcode
  17. PHP內核探索:解釋器的執行過程
  18. PHP內核探索:變量概述
  19. PHP內核探索:變量存儲與類型
  20. PHP內核探索:PHP中的哈希表
  21. PHP內核探索:理解Zend里的哈希表
  22. PHP內核探索:PHP哈希算法設計
  23. PHP內核探索:翻譯一篇HashTables文章
  24. PHP內核探索:哈希碰撞攻擊是什么?
  25. PHP內核探索:常量的實現
  26. PHP內核探索:變量的存儲
  27. PHP內核探索:變量的類型
  28. PHP內核探索:變量的值操作
  29. PHP內核探索:變量的創建
  30. PHP內核探索:預定義變量
  31. PHP內核探索:變量的檢索
  32. PHP內核探索:變量的類型轉換
  33. PHP內核探索:弱類型變量的實現
  34. PHP內核探索:靜態變量的實現
  35. PHP內核探索:變量類型提示
  36. PHP內核探索:變量的生命周期
  37. PHP內核探索:變量賦值與銷毀
  38. PHP內核探索:變量作用域
  39. PHP內核探索:詭異的變量名
  40. PHP內核探索:變量的value和type存儲
  41. PHP內核探索:全局變量Global
  42. PHP內核探索:變量類型的轉換
  43. PHP內核探索:內存管理開篇
  44. PHP內核探索:Zend內存管理器
  45. PHP內核探索:PHP的內存管理
  46. PHP內核探索:內存的申請與銷毀
  47. PHP內核探索:引用計數與寫時復制
  48. PHP內核探索:PHP5.3的垃圾回收機制
  49. PHP內核探索:內存管理中的cache
  50. PHP內核探索:寫時復制COW機制
  51. PHP內核探索:數組與鏈表
  52. PHP內核探索:使用哈希表API
  53. PHP內核探索:數組操作
  54. PHP內核探索:數組源碼分析
  55. PHP內核探索:函數的分類
  56. PHP內核探索:函數的內部結構
  57. PHP內核探索:函數結構轉換
  58. PHP內核探索:定義函數的過程
  59. PHP內核探索:函數的參數
  60. PHP內核探索:zend_parse_parameters函數
  61. PHP內核探索:函數返回值
  62. PHP內核探索:形參return value
  63. PHP內核探索:函數調用與執行
  64. PHP內核探索:引用與函數執行
  65. PHP內核探索:匿名函數及閉包
  66. PHP內核探索:面向對象開篇
  67. PHP內核探索:類的結構和實現
  68. PHP內核探索:類的成員變量
  69. PHP內核探索:類的成員方法
  70. PHP內核探索:類的原型zend_class_entry
  71. PHP內核探索:類的定義
  72. PHP內核探索:訪問控制
  73. PHP內核探索:繼承,多態與抽象類
  74. PHP內核探索:魔術函數與延遲綁定
  75. PHP內核探索:保留類與特殊類
  76. PHP內核探索:對象
  77. PHP內核探索:創建對象實例
  78. PHP內核探索:對象屬性讀寫
  79. PHP內核探索:命名空間
  80. PHP內核探索:定義接口
  81. PHP內核探索:繼承與實現接口
  82. PHP內核探索:資源resource類型
  83. PHP內核探索:Zend虛擬機
  84. PHP內核探索:虛擬機的詞法解析
  85. PHP內核探索:虛擬機的語法分析
  86. PHP內核探索:中間代碼opcode的執行
  87. PHP內核探索:代碼的加密與解密
  88. PHP內核探索:zend_execute的具體執行過程
  89. PHP內核探索:變量的引用與計數規則
  90. PHP內核探索:新垃圾回收機制說明

總結

以上是生活随笔為你收集整理的PHP内核中的哈希表结构的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。