Java容器——HashMap(Java8)原始碼解析(一)
一 概述
HashMap是最常用的Java資料結構之一,是一個具有常數級別的存取速率的高效容器。相對於List,Set等,結構相對複雜,本篇我們先對HashMap的做一個基本說明,對組成元素和構造方法進行介紹。
二 繼承關係
首先看HashMap的繼承關係,比較簡單,實現了Map和序列化等。
圖1 HashMap繼承關係圖
HashMap繼承自Map,Map作為一個重要的介面,很有必要需要介紹一下。
圖2 Map介面
Map介面定義了一些通用方法,包括插入,刪除,替換,遍歷元素等常規集合方法。這裡有必要重要關注的有:
1 Entry介面:
Entry<K,V>是Map元素的組成形式,它是一個鍵值對,Key是Map的索引,Value是儲存的元素。由於Key是為了便於快速查詢,並且能夠唯一標識,所以推薦使用不變類,如String來做鍵,若是自己實現的類,則必須重寫hashcode和equals方法。Entry是Map元素的組成形式,可以並且只能通過iterator來遍歷。
2 forEach方法:
default void forEach(BiConsumer<? super K, ? super V> action) { Objects.requireNonNull(action); for (Map.Entry<K, V> entry : entrySet()) { K k; V v; try { k = entry.getKey(); v = entry.getValue(); } catch (IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } action.accept(k, v); } }
注意著是一個介面中的default方法。Java自1.8以後支援在介面中實現預設方法,不同於抽象方法,子類必須重新實現,default方法是開箱即用。這裡可以看到,forEach方法是對Entry進行遍歷並且執行指定操作。
三 組成元素
3.1 類例項變數
/**
* Entry陣列
*/
transient Node<K,V>[] table;
/**
* Entry集合
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* Entry的數量
*/
transient int size;
/**
* HashMap被修改的次數
*/
transient int modCount;
/**
* 擴容閾值
*
*/
int threshold;
/**
* HashMap的裝載因子
*
*/
final float loadFactor;
可以看到,這其中的變數和HashTable中基本一致,事實上,HashMap就是HashTable的去同步鎖以及提升單節點效率的優化版。為何需要提生效率,一方面同步操作沒有必要使用synchronisd這種重量級鎖,另一方面,HashTable的設計方式可能會發生效能機具下降。
需要注意的變數關係是capability * loadFactor = threshold。翻譯一下就是HashMap的擴容閾值是當前容量乘以承載因子。這個閾值不是table中的下標數量,而是整個HashMap已經裝載的元素。
圖2 HashTable出現極端Key碰撞
當HashTable的Key碰撞了以後,會在單一Node節點處形成單向連結串列。所以假設Key選取的不是很合適,衝突很多,HashTable就退化成LinkedList了,查詢效率和插入效率都劇烈下降,這也背離了設計的初衷。
HashMap要如何解決這個問題呢,可以從下面定義的變數中一窺一二。
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 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;
/**
* 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;
/**
* 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;
為了避免出現HashMap變連結串列,HashMap引入了紅黑樹。紅黑樹是一種儘量保持平衡的搜尋二叉樹,簡單而言對紅黑樹的增刪改查都可以再O(lgn)時間內完成,較連結串列的O(n)有了巨大的提升,詳細瞭解見紅黑樹維基百科。
這些變數也說明了連結串列與紅黑樹相互轉化的條件:
1 當連結串列長度超過TREEIFY_THRESHOLD時,同時滿足capacity大於MIN_TREEIFY_CAPACITY時,連結串列轉化為樹;
2 當樹節點少於UNTREEIFY_THRESHOLD時,從樹轉化為連結串列。
四 建構函式
HashMap的初始化構造方法有四個,都是圍繞initialCapacity和loadFactor這兩個變數展開的,由此可見這兩個變數的重要性,最後一個是根據已有的Map集合初始化新的Map。
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
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);
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
到這兒了,基本把HashMap的關鍵元素介紹完了,接下來就是HashMap的具體實現了。那麼HashMap究竟有哪些關鍵操作,並且是如何實現的,請看HashMap(Java8)原始碼解析(二)。