HashMap在Java開發中使用的很是頻繁,能夠說僅次於String,能夠和ArrayList並駕齊驅,準備用幾個章節來梳理一下HashMap。咱們仍是從定義一個HashMap開始。java
HashMap<String, Integer> mapData = new HashMap<>();
咱們今後處進入源碼,逐步揭露HashMap算法
/** * 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 }
咱們發現了兩個變量loadFactor和DEFAULT_LOAD_FACTOR,從命名方式來看:由於沒有接收到loadFactor參數,從而將某個默認值賦值給了loadFactor。這兩變量究竟是什麼意思,還有無其餘變量?數組
其實HashMap中定義的靜態變量和成員變量不少,咱們看一下安全
//靜態變量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64;
//成員變量 transient Node<K,V>[] table; transient Set<Map.Entry<K,V>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor;
共有6個靜態變量,都設置了初始值,且被final修飾,叫常量更合適,它們的做用其實也能猜出來,就是用於成員變量的默認值設定以及方法中相關的條件判斷等狀況。數據結構
共有6個成員變量,除這些成員變量外,還有一個重要概念capacity,咱們主要說一下table,entrySet,capacity, size,threshold,loadFactor,咱們咱們簡單解釋一下它們的做用。this
table變量爲HashMap的底層數據結構,用於存儲添加到HashMap中的Key-value對,是一個Node數組,Node是一個靜態內部類,一種數組和鏈表相結合的複合結構,咱們看一下Node類:設計
static 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; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
若以伺機作比喻的話,那麼你買票的身份證號就是(key),經過hash算法生成的(hash)值就至關於值機後獲得的航班座位號;你本身天然就是(value),你旁邊的座位、人就是下一個Node(next);這樣的一個座位總體(包括所坐人員及其身份證號、座位號)就是一個table,這許多的table的構建的Node[] table,就構成了本次航班任務。code
那麼爲何要用到數組和鏈表結合的數據結構?blog
咱們知道數組和鏈表都有其各自的優勢和缺點,數組連續存儲,尋址容易,插入刪除操做相對困難;而鏈表離散存儲,尋址相對困難,而插入刪除操做容易;而HashMap結合了這兩種數據結構,保留了各自的優勢,又彌補了各自的缺點,固然鏈表長度太長的話,在JDK8中會轉化爲紅黑樹,紅黑樹在後面的TreeMap章節在講解。接口
HashMap的結構圖以下:
怎麼解釋這種結構呢?
仍是以伺機爲例來講明,假如購票系統比較人性化並取消了值機操做,購票按照年齡段進行了區分,方便你們旅途溝通交流,因而20歲如下共6我的的分爲了一組在20A~20F,20~30歲共6我的分爲一組在21A~21F,30~40歲共6我的分爲一組在22A~22F,40~50歲共6我的分爲一組在23A~23F。
這時咱們若是要找20幾歲的小姐姐,咱們很容易知道去21排找,從21A開始往下找,應該就能很快找到。
從數據的角度看,按年齡段分組(經過hash算法獲得hash值,不一樣年齡段hash值不一樣,相同年齡段hash值相同)後,將各年齡段中第一個坐到座位上的人放到數組table中,下一我的來的時候,將第一我的往裏面挪,本身在數組裏,並將next指向第一我的。
entrySet變量爲EntrySet實體,定義爲變量可保證不重複屢次建立,是一個Map.Entry的集合,Map.Entry<K,V>是一個接口,Node類就實現了該接口,所以EntrySet中方法須要操做的數據就是HashMap的Node實體。
public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; }
capacity並非一個成員變量,但HashMap中不少地方都會使用到這個概念,意思是容量,很好理解,在前面的文中提到了兩個常量都與之相關
/** * The default initial capacity - MUST be a power of two(必須爲2的冪次). * 默認容量16,舉例:飛機上正常的座位所對應的人員數量, */ 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(必須爲2的冪次,且不能大於最大容量1,073,741,824). * 舉例:緊急狀況下,如救災時儘量快撤離人員,這個時候在保證安全的狀況下(容許站立),能運輸的人員數 */ static final int MAXIMUM_CAPACITY = 1 << 30;
同時HashMap還具備擴容機制,容量的規則爲2的冪次,即capacity能夠是1,2,4,8,16,32...,怎麼實現這種容量規則呢?
/** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
用該方法便可找到傳遞進來的容量的最近的2的冪次,即
cap = 2, return 2;
cap = 3, return 4;
cap = 9, return 16;
...
你們能夠傳遞值進去本身算一下,先cap-1操做,是由於當傳遞的cap自己就是2的冪次狀況下,假如爲4,不減去一最後獲得的結果將是傳遞的cap的2倍。
咱們來一行行計算一下:tableSizeFor(11),按規則最後獲得的結果應該是16
//第一步:n = 10,轉爲二進制爲00001010 int n = cap - 1; //第二步:n右移1位,高位補0(10進制:5,二進制:00000101),並與n作或運算(有1爲1,同0爲0),而後賦值給n(10進制:15,二進制:00001111) n |= n >>> 1; //第三步:n右移2位,高位補0(10進制:3,二進制:00000011),並與n作或運算(有1爲1,同0爲0),而後賦值給n(10進制:15,二進制:00001111) n |= n >>> 2; //第四步:n右移4位,高位補0(10進制:0,二進制:00000000),並與n作或運算(有1爲1,同0爲0),而後賦值給n(10進制:15,二進制:00001111) n |= n >>> 4; //第五步:n右移8位,高位補0(10進制:0,二進制:00000000),並與n作或運算(有1爲1,同0爲0),而後賦值給n(10進制:15,二進制:00001111) n |= n >>> 8; //第六步:n右移16位,高位補0(10進制:0,二進制:00000000),並與n作或運算(有1爲1,同0爲0),而後賦值給n(10進制:15,二進制:00001111) n |= n >>> 16; //第七步:return 15+1 = 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
最終的結果正如預期,算法很牛逼啊,ヽ(ー_ー)ノ,能看懂,但卻設計不出來。
size變量記錄了Map中的key-value對的數量,在調用putValue()方法以及removeNode()方法時,都會對其形成改變,和capacity區分一下便可。
threshold爲臨界值,顧名思義,當過了臨界值就須要作一些操做了,在HashMap中臨界值「threshold = capacity * loadFactor」,當超過臨界值時,HashMap就該擴容了。
loadFactor爲裝載因子,就是用來衡量HashMap滿的程度,默認值爲DEFAULT_LOAD_FACTOR,即0.75f,可經過構造器傳遞參數調整(0.75f已經很合理了,基本沒人會去調整它),很好理解,舉個例子:
100分的試題,父母只須要你考75分,就給你買一臺你喜歡的電腦,裝載因子就是0.75,75分就是臨界值;若是幾年後,試題的分數變成200分了,這個時候就須要你考到150分才能獲得你喜歡的電腦了。
本文主要講解了HashMap中的一些主要概念,同時對其底層數據結構從源碼的角度進行了分析,table是一個數據和鏈表的複合結構,size記錄了key-value對的數量,capacity爲HashMap的容量,其容量規則爲2的冪次,loadFactor爲裝載所以,衡量滿的程度,而threshold爲臨界值,當超出臨界值時就會擴容。