Java学习笔记: HashMap 和 HashSet
📒數(shù)據(jù)結構專欄簡介: 內(nèi)容主要以介紹數(shù)據(jù)結構為主, 希望讀者看了之后能有所收獲🉐
👷🏻?作者也是一名剛上路的小白coder🛵, 歡迎各位評論, 批評指正或私信交流👁?🗨~~
文章目錄
- HashMap
- 存儲結構
- 常用方法
- 構造方法
- 方法
- HashSet介紹
- 存儲結構
- 常用方法
- 構造方法
- 方法
- 哈希表
- 基本操作(CURD)
- 插入(Create) :
- 搜索(Read):
- 更新(Update):
- 刪除(Delete)
- 哈希沖突
- 哈希函數(shù)設計
- 直接定制法
- 除留余數(shù)法
- 負載因子的調(diào)節(jié)
- 哈希沖突的解決辦法
- 閉散列法
- 開散列法
關于Map接口和Set接口在上一篇關于TreeMap文章中已經(jīng)介紹過, 這里不再贅述, 詳見: http://t.csdn.cn/L44vx
HashMap
HashMap是Map接口下的一個實現(xiàn)子類, HashMap與TreeMap一樣, 同樣存儲的是<Key, Value>的鍵值對, 與 TreeMap 相同的是, HashMap中的Key 也是唯一的, 當有相同的Key的<Key, Value>, 會更新之前的Value(若Key已存在)
存儲結構
HashMap的存儲容器通常是一個哈希桶, 在存儲的元素過多時, 容器的結構會轉換成一個類似于TreeMap的樹狀結構. 樹中的元素排序依據(jù)主要是哈希值(如果元素對應的類實現(xiàn)了Comparable接口則基于compareTo方法進行比較排序)
我們可以觀察HashMap的源碼進行學習
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;哈希表的初始容量是16
static final int MAXIMUM_CAPACITY = 1 << 30;最大容量是 1 << 30(1073741824)
/** The bin count threshold for using a tree rather than list for a bin. Bins are converted to trees when adding an element to a bin with at least this many nodes. The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon shrinkage. */ static final int TREEIFY_THRESHOLD = 8;樹化的閾值是8, 該閾值必須大于2, 且最小應該是8, 只有這樣才匹配樹容器和普通容器間去樹化轉換的收縮前提
/** The bin count threshold for untreeifying a (split) bin during a resize operation. Should be less than TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal. */ static final int UNTREEIFY_THRESHOLD = 6;解除樹化的閾值是6, 該閾值應該要小于樹化閾值, 且也要與收縮檢測相匹配
/** The smallest table capacity for which bins may be treeified. (Otherwise the table is resized if too many nodes in a bin.) Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between resizing and treeification thresholds. */ static final int MIN_TREEIFY_CAPACITY = 64;可能樹化的最小容量是64, 而哈希表的擴容大小最小值至少為4*樹化的最小容量
/** The load factor used when none specified in constructor.*/ static final float DEFAULT_LOAD_FACTOR = 0.75f;默認的負載因子是0.75
負載因子定義:
哈希表的負載因子 α=表中元素個數(shù)表的長度(容量)α = \frac {表中元素個數(shù)}{表的長度(容量)}α=表的長度(容量)表中元素個數(shù)?
當負載因子過大時, 插入新元素時就容易發(fā)生沖突
常用方法
構造方法
| HashMap() | 構造一個具有默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap |
| HashMap(int initialCapacity) | 構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap |
| HashMap(int initialCapacity, float loadFactor) | 構造一個帶指定初始容量和負載因子的空 HashMap |
| HashMap(Map<? extends K,? extends V> m) | 構造一個映射關系與指定 Map 相同的新 HashMap |
方法
| void clear() | 從map中移除所有映射關系 |
| boolean containsKey(Object key) | 如果map中包含對于指定鍵的映射關系,則返回 true |
| boolean containsValue(Object value) | 如果map中將一個或多個鍵映射到指定值,則返回 true |
| Set<Map.Entry<K,V>> entrySet() | 返回map中所包含的映射關系的 Set 視圖 |
| V get(Object key) | 返回指定鍵所映射的值;如果對于該鍵來說,map中不包含任何映射關系,則返回 null |
| V put(K key, V value) | 在此map中關聯(lián)指定值與指定鍵 |
| V remove(Object key) | 從此映射中移除指定鍵的映射關系(如果存在) |
| boolean isEmpty() | 如果map中不包含鍵-值映射關系,則返回 true |
| int size() | 返回map中的鍵-值映射關系數(shù) |
Map.Entry<K,V>是一個接口, 詳情可見TreeMap介紹文章
HashSet介紹
HashSet實現(xiàn)了Set接口, 存儲的是Key, Key是唯一的
存儲結構
/*** This class implements the <tt>Set</tt> interface, backed by a hash table* (actually a <tt>HashMap</tt> instance). It makes no guarantees as to the* iteration order of the set; in particular, it does not guarantee that the* order will remain constant over time. This class permits the <tt>null</tt>* element. */由源碼的注釋我們可以知道HashSet是借HashMap實現(xiàn)的, HashSet中的元素排序順序是不能保證的, 且允許儲存null
常用方法
構造方法
| HashSet() | 構造一個新的空 set,其底層 HashMap 實例的默認初始容量是 16,加載因子是 0.75 |
| HashSet(Collection<? extends E> c) | 構造一個包含指定 collection 中的元素的新 Set |
| HashSet(int initialCapacity) | 構造一個新的空 set,其底層 HashMap 實例具有指定的初始容量和默認的負載因子 |
| HashSet(int initialCapacity, float loadFactor) | 構造一個新的空 Set,其底層 HashMap 實例具有指定的初始容量和指定的負載因子 |
方法
| boolean add(E e) | 如果 set 中尚未包含指定元素,則添加指定元素 |
| void clear() | 從 set 中移除所有元素 |
| boolean contains(Object o) | 如果 set 中包含指定元素,則返回 true |
| boolean isEmpty() | 如果 set 中不包含任何元素,則返回 true |
| boolean remove(Object o) | 如果指定元素存在于此 set 中,則將其移除 |
| int size() | 返回此 set 中的元素的數(shù)量(set 的容量) |
哈希表
一般的數(shù)據(jù)結構如列表, 鏈表等進行搜索操作時是需要通過遍歷整個表(O(n))才能完成的, 而平衡樹的時間復雜度也是與樹的高度有關(O(logn)), 而我們想要的是一種更高效, 更理想的搜索模式, 就如狙擊槍一般指哪打哪, 達到近似(O(1))的效率, 而Hash Table正是為此而生的.
哈希表(又稱散列表)中通過構造元素中關鍵碼和元素的存儲位置一一對應的映射關系, 使其簡化搜索的過程
基本操作(CURD)
-
插入(Create) :
計算出元素對應的哈希值, 依據(jù)哈希值將元素存儲到對應位置
-
搜索(Read):
根據(jù)搜索元素中的關鍵字計算出哈希值, 將對應的存儲的元素的哈希值進行比較, 相同則返回其引用
-
更新(Update):
與搜索類似, 在搜索的基礎下, 對應哈希值相同則更新
-
刪除(Delete)
與搜索類似, 在搜索的基礎下, 對應哈希值相同則將該引用置空
哈希沖突
當哈希表中的元素越來越多時, 總會發(fā)生的情況: 有兩個或多個元素計算出的哈希值而指向同一塊存儲空間, 這就是發(fā)生了哈希沖突(哈希碰撞), 發(fā)生的碰撞越多自然也會降低效率, 這是我們不愿看到的
哈希函數(shù)設計
為了降低發(fā)生哈希沖突的頻率, 在哈希函數(shù)設計上我們可以下一些功夫, 常用的哈希函數(shù)的設計有:直接定制法, 除留余數(shù)法, 平方取中法, 折疊法, 數(shù)學分析法, 在這就簡單介紹兩種常用的方法 直接定制法 和 除留余數(shù)法, 其他方法讀者若有興趣可以自行查找資料, 優(yōu)秀哈希函數(shù)設計可以有效降低哈希沖突發(fā)生的頻率, 但無法完全避免哈希沖突的發(fā)生
直接定制法
取元素中的關鍵字為變量, 通過線性函數(shù)運算得到哈希值(散列地址), 如 :
Hash (Key) = A * Key + B
這種辦法的優(yōu)點是比較簡單, 適用于已知關鍵字范圍, 計算出的地址要數(shù)量不大且分布比較連續(xù)
除留余數(shù)法
同理取元素中的關鍵字為變量, 設哈希表的容量為max, 取最接近或者等于max的質(zhì)數(shù)p(p ≤ m)作為除數(shù), 如 :
Hash (Key) = Key % p
這種方法的優(yōu)缺點與上述方法相似
負載因子的調(diào)節(jié)
哈希沖突發(fā)生的頻率與負載因子也有關, 當哈希表中存儲的元素越多, 負載因子越大, 發(fā)生碰撞的概率也就越高. 所以要對負載因子進行調(diào)節(jié), 從上述負載因子的定義來看, 調(diào)節(jié)負載因子可以改變哈希表的容量
要注意的是調(diào)節(jié)哈希表容量時, 若設計的哈希函數(shù)與哈希表的容量有關, 要將其中存儲的元素重新進行散列地址的計算重新存儲
哈希沖突的解決辦法
閉散列法
當發(fā)生哈希沖突時, 若哈希表的存儲元素個數(shù)還未達到最大, 我們總能找到空位存放發(fā)生沖突的元素
- 線性探測: 當發(fā)生哈希沖突時, 依次往后尋找到空位即可
如圖上所示, 假設哈希函數(shù)是Hash(Key) = Key % 8, 要插入Key 為 1 的元素, Index 為 1 處已經(jīng)存有 Key 為 9 的元素, 當我們想存入Key為 1 的元素時, 就會發(fā)生沖突, 此時依次往后尋找到 Index 為 2 時有空位, 插入即可
- 二次探測: 當發(fā)生哈希沖突時, 尋找下一個位置的方法為Hash(Key)=(H0+i2)Hash(Key) = (H_{0} + i^{2} )Hash(Key)=(H0?+i2)% m (H0H_{0}H0?是初次計算出的哈希值, 式中也可以將 H0H_{0}H0? 與 i2i^{2}i2 相減)
如上圖所示, 依然假設哈希函數(shù)是Hash(Key) = Key % 8, 要插入Key 為 17 的元素, Index 為 1 處已經(jīng)存有元素, 通過上述公式計算得下一個 Index 為 2 處, 也存有元素, 則計算第二次得Index 為 5, 為空可插入
開散列法
開散列法則是利用類似鏈表的結構處理沖突, 創(chuàng)建一個存儲哈希桶引用的容器, 當發(fā)生沖突時, 只需用頭插法處理即可
總結
以上是生活随笔為你收集整理的Java学习笔记: HashMap 和 HashSet的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OFF文件格式_拔剑-浆糊的传说_新浪博
- 下一篇: java美元兑换,(Java实现) 美元