前提:學習HashMap的底層代碼以前,首先要對數據結構要個大體的瞭解。其中重點了解數組,鏈表,樹的概念和用法。數組
(1).圖示爲JDK1.8以前的HashMap結構。數組+鏈表,數組中的元素爲鏈表的頭節點。若是不一樣的key對應相同的hash值,則會在頭節點後造成鏈表。
經過代碼的實現,咱們能夠分析出:若是在儲存數據時,某一個鏈表過長,則會影響查詢性能。(下面會分析put和get方法,解釋鏈表過長如何影響性能)數據結構
(2).JDK1.8中進行了優化。當鏈表長度即將超過閥值(TREEIFY_THRESHOLD),會把鏈表轉化爲紅黑樹。底層實現變爲數組+鏈表+紅黑樹
函數
(1).數組和鏈表中的每一個元素都是Node<K,V>對象,它是HashMap的一個內部類而且實現了Map.Entry<K,V>。其中包含了幾個重要屬性,int hash; K key; V value; Node<K,V> next;性能
static class Node<k,v> implements Map.Entry<k,v> { final int hash;//經過key的hashCode方法獲取的hash值。hash值相同的key會在數組中找到同一個位置 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; } }
(2).當鏈表長度達到閥值,鏈表轉化爲紅黑樹。此時數組和紅樹中的元素是TreeNode<K,V>學習
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> { TreeNode<k,v> parent; // 父節點 TreeNode<k,v> left; //左子樹 TreeNode<k,v> right;//右子樹 TreeNode<k,v> prev; // needed to unlink next upon deletion boolean red; //顏色屬性 TreeNode(int hash, K key, V val, Node<k,v> next) { super(hash, key, val, next); } }
(3)HashMap中幾個經常使用的屬性和構造方法優化
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 序列號 private static final long serialVersionUID = 362498820763181265L; // 默認的初始容量是16,經過移位運算得到結果。在計算機底層位運算速度最快 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 最大容量 2的30次方 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; // 鏈表轉化爲紅黑樹對應的table的最小值 static final int MIN_TREEIFY_CAPACITY = 64; // 存儲節點的數組,必定是2的冪次方 transient Node<k,v>[] table; // transient Set<map.entry<k,v>> entrySet; // 數組中元素的個數,注意這個不是數組的長度。 transient int size; // 修改HashMap的次數 transient int modCount; // 閥值,當數組中的元素大於threshold時HashMap進行擴容 threshold = (數組長度)capacity * loadFactor int threshold; // 填充因子 final float loadFactor; //無參構造函數,比較經常使用 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; } //一個參數,數組容量 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } //兩個參數,數組容量和加載因子 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; //由於咱們的入參指定了數組大小,可是傳入的值可能不知足HashMap要求。 //所以HashMap使用tableSizeFor保證數組大小必定是2的n次冪 this.threshold = tableSizeFor(initialCapacity); } /** 此方法的目的是:保證有參構造傳入的初始化數組長度是>=cap的最小2的冪次方。 對n不斷的無符號右移而且位或運算,能夠將n從最高位爲1開始的全部右側位數變成1, 最後n+1即成爲大於cap的最小2的冪次方。 第一行 n=cap-1 是爲了保證若是cap自己就是2^n,那麼結果也將是其自己 */ 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; } //傳入一個HashMap實例 public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } //若是傳入的HashMap中有元素,遍歷它而且把其中的元素put到當前HashMap中 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { //當前數組爲空 float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t);//計算數組長度,而且保證長度是2的冪次方 } else if (s > threshold) resize(); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } } }
未完待續this