java-HashMap源码学习
閱讀提示:HashMap源碼在不同版本情況下,具體源碼可能不一樣(優化問題),但功能幾乎是相同的(博主1.8)
什么是Hash?
hash表是一種數據結構,它擁有驚人的效率,它的時間復雜度低到接近O(1)這樣的常數級。
hash表的實現主要是:
1.計算存儲位置的hash函數。
2.處理哈希沖突的方法。
3.hash的物理存儲。
hash函數
它的目的是通過一個key選出(映射)一個唯一的存儲地址。
最常見的hash函數:f(key)=a*key+b
這里a,b為常數(不為0),f(key)就是計算出的哈希值
一般一個hash函數的設計好壞,直接影響到效率。
哈希沖突
hash沖突定義:當兩個key相同時計算的hash值會一樣,導致沖突。
hash沖突解決部分方法:
**開放定地址:**找下一塊地址(另一個hash表(另一個未用過的hash值·),無限多)
**再散列函數法:**一個hash函數解決不了,就兩個,兩個不行就三個…
**鏈地址法(hash桶(此處鏈表)的概念):**數組+鏈表,將具有相同hash的同義詞按次序放入鏈表,并鏈表存在數組。鏈表的hash值以數組hash值開始確定新的hash(不擔心與其它數組的hash相同)
還有的就不做介紹了
物理存儲
物理存儲結構:順序|鏈式存儲
hash表的主干永遠都是一個數組
一般的hash只需要一個數組來存儲,一般以數組下標做hash值。
在鏈地址法中需要用到鏈表。
什么是Map?
Map在計算機概念是一個key-value(唯一性)鍵值對
什么是HashMap?
根據前面的鋪墊,
hashMap的存儲主干是一個數組(源碼中的Node(有些是Entry)對象數組),
Node(Entry)對象包含了Key-Value屬性
hashMap處理hash沖突:java1.8后,使用的是鏈地址法。
數據存儲結構如下:
在擁有鏈表的情況下,hashMap的查詢效率必然是低一些的,復雜度提高到o(n)
但1.8利用了紅黑樹數據結構,又將復雜度降為了o(Log(n))
HashMap的源碼分析
1.構造函數(4個)
部分成員變量:
///
transient 關鍵字:再被修飾后變量序列化將不可見
///
threshold:初始空間(initialCapacity傳入參數)
loadFactor:負載因子
modCount:是快速失敗的判斷標準(在迭代時,其他線程訪問Map,并導致其結構改變,會拋異常)
table:節點Set集合(HashMap主干,是個數組)
initialCapacity默認為16,loadFactory默認為0.75
負載因子在0.75時效率最好(數學統計學驗證),用于衡量空間利用的方法選擇
hashMap會擴容且容量永遠是2的冪
合理判斷參數,就結束構造public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}合理判斷空間大小參數,使用默認負載參數就結束構造public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}/使用默認參數就結束構造public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}構造一個具有相同的映射關系與指定Map一個新的HashMap。 HashMap中與默認負載因數(0.75)和初始容量足以容納在指定的地圖的映射創建public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}發現此時table變量未分配空間。根據put函數源碼發現,table變量在put()分配空間
再看關鍵對象Node(Entry)
//Map.Entry<K,V>是一個接口//Nodestatic class Node<K,V> implements Map.Entry<K,V> {final int hash;//哈希值final K key;//鍵值V value;Node<K,V> next;//下一節點;因為是鏈地址法Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}//other}再看其部分關鍵函數
hash() : key.hashCode()產生依靠equals()方法,位移異或等操作的哈希值
,這個根據版本不同也計算方法不同,但是hashCode()幾乎一直存在
put() : 計算hash值然后對table
JDK1.8在JDK1.7的基礎上針對增加了紅黑樹來進行優化。即當鏈表超過8時,鏈表就轉換為紅黑樹,利用紅黑樹快速增刪改查的特點提高HashMap的性能,其中會用到紅黑樹的插入、刪除、查找等算法。
看上面的put函數的putVal()就含有紅黑樹的插入方法,
一開始普通的鏈表不需要紅黑樹。
下列源碼可以看出,
轉換紅黑樹條件是
鏈表(是在每次遍歷到數組時每個單元對于的鏈表,參考上圖)節點數大于等于8且數組長度大于等于64,
詳見方法:treeifyBin();
//判斷處理方法final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();else if ((e = tab[index = (n - 1) & hash]) != null) {TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null);if (tl == null)hd = p;else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null)hd.treeify(tab);}} //紅黑樹構造類 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unlink next upon deletionboolean red;//是紅節點嗎TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);}}HashMap源碼探究到此為止,以后可以繼續深入,學習紅黑樹的實際應用等等
在此附上一張網上Copy過來的圖:
是講1.8版本的HashMap在增加元素后一系列的操作步驟,以及優化方式流程圖
集合源碼魅力無窮。
踏踏實實讀源碼。
總結
以上是生活随笔為你收集整理的java-HashMap源码学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java-List集合的源码分析(数据结
- 下一篇: java-Set集合源码学习