Java集合的學習先理清數據結構:html
//哈希桶,存放鏈表。 長度是2的N次方,或者初始化時爲0. transient Node<K,V>[] table; //最大容量 2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; //默認的加載因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //加載因子,用於計算哈希表元素數量的閾值。 threshold = 哈希桶.length * loadFactor; final float loadFactor; //哈希表內元素數量的閾值,當哈希表內元素數量超過閾值時,會發生擴容resize()。 int threshold;
public HashMap() { //默認構造函數,賦值加載因子爲默認的0.75f this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(int initialCapacity) { //指定初始化容量的構造函數 this(initialCapacity, DEFAULT_LOAD_FACTOR); } //同時指定初始化容量 以及 加載因子, 用的不多,通常不會修改loadFactor public HashMap(int initialCapacity, float loadFactor) { //邊界處理 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量最大不能超過2的30次方 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //顯然加載因子不能爲負數 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //設置閾值爲>=初始化容量的 2的n次方的值 this.threshold = tableSizeFor(initialCapacity); } //新建一個哈希表,同時將另外一個map m 裏的全部元素加入表中 public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
getnode
/** * get * 1.先從數組中取,取到hash值相等且equals的,直接返回 * 2.先從數組中取,取到hash值相等且!equals,到鏈表/紅黑樹中取 */ // 每個節點結構 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 V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab;//Entry對象數組 Node<K,V> first,e; //在tab數組中通過散列的第一個位置 int n; K k; /*找到插入的第一個Node,方法是hash值和n-1相與,tab[(n - 1) & hash]*/ //也就是說在一條鏈上的hash值相同的 if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) { /*檢查第一個Node是否是要找的Node*/ if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k))))//判斷條件是hash值要相同,key值要相同 return first; /*檢查first後面的node*/ if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); /*遍歷後面的鏈表,找到key值和hash值都相同的Node*/ do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
put面試
/** * put * 1.數組下標沒有對應hash值,直接newNode()添加 * 2.數組下標有對應hash值,添加到鏈表最後 * 3.鏈表超過最大長度(8),將鏈表改成紅黑樹再添加元素 */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //tab存放 當前的哈希桶, p用做臨時鏈表節點 Node<K,V>[] tab; Node<K,V> p; int n, i; //若是當前哈希表是空的,表明是初始化 if ((tab = table) == null || (n = tab.length) == 0) //那麼直接去擴容哈希表,而且將擴容後的哈希桶長度賦值給n n = (tab = resize()).length; //若是當前index的節點是空的,表示沒有發生哈希碰撞。 直接構建一個新節點Node,掛載在index處便可。 //這裏再囉嗦一下,index 是利用 哈希值 & 哈希桶的長度-1,替代模運算 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//不然 發生了哈希衝突。 //e Node<K,V> e; K k; //若是哈希值相等,key也相等,則是覆蓋value操做 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//將當前節點引用賦值給e else if (p instanceof TreeNode)//紅黑樹暫且不談 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//不是覆蓋操做,則插入一個普通鏈表節點 //遍歷鏈表 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) {//遍歷到尾部,追加新節點到尾部 p.next = newNode(hash, key, value, null); //若是追加節點後,鏈表數量》=8,則轉化爲紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //若是找到了要覆蓋的節點 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //若是e不是null,說明有須要覆蓋的節點, if (e != null) { // existing mapping for key //則覆蓋節點值,並返回原oldValue V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; //這是一個空實現的函數,用做LinkedHashMap重寫使用。 afterNodeAccess(e); return oldValue; } } //若是執行到了這裏,說明插入了一個新的節點,因此會修改modCount,以及返回null。 //修改modCount ++modCount; //更新size,並判斷是否須要擴容。 if (++size > threshold) resize(); //這是一個空實現的函數,用做LinkedHashMap重寫使用。 afterNodeInsertion(evict); return null; }
remove數組
/** * reomve */ public V remove(Object key) { Node<K, V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } final Node<K, V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K, V>[] tab; Node<K, V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K, V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K, V>) p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key .equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value .equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
resize數據結構
final Node<K,V>[] resize() { //oldTab 爲當前表的哈希桶 Node<K,V>[] oldTab = table; //當前哈希桶的容量 length int oldCap = (oldTab == null) ? 0 : oldTab.length; //當前的閾值 int oldThr = threshold; //初始化新的容量和閾值爲0 int newCap, newThr = 0; //若是當前容量大於0 if (oldCap > 0) { //若是當前容量已經到達上限 if (oldCap >= MAXIMUM_CAPACITY) { //則設置閾值是2的31次方-1 threshold = Integer.MAX_VALUE; //同時返回當前的哈希桶,再也不擴容 return oldTab; }//不然新的容量爲舊的容量的兩倍。 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)//若是舊的容量大於等於默認初始容量16 //那麼新的閾值也等於舊的閾值的兩倍 newThr = oldThr << 1; // double threshold }//若是當前表是空的,可是有閾值。表明是初始化時指定了容量、閾值的狀況 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr;//那麼新表的容量就等於舊的閾值 else {}//若是當前表是空的,並且也沒有閾值。表明是初始化時沒有任何容量/閾值參數的狀況 // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY;//此時新表的容量爲默認的容量 16 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的閾值爲默認容量16 * 默認加載因子0.75f = 12 } if (newThr == 0) {//若是新的閾值是0,對應的是 當前表是空的,可是有閾值的狀況 float ft = (float)newCap * loadFactor;//根據新表容量 和 加載因子 求出新的閾值 //進行越界修復 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //更新閾值 threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) //根據新的容量 構建新的哈希桶 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //更新哈希桶引用 table = newTab; //若是之前的哈希桶中有元素 //下面開始將當前哈希桶中的全部節點轉移到新的哈希桶中 if (oldTab != null) { //遍歷老的哈希桶 for (int j = 0; j < oldCap; ++j) { //取出當前的節點 e Node<K,V> e; //若是當前桶中有元素,則將鏈表賦值給e if ((e = oldTab[j]) != null) { //將原哈希桶置空以便GC oldTab[j] = null; //若是當前鏈表中就一個元素,(沒有發生哈希碰撞) if (e.next == null) //直接將這個元素放置在新的哈希桶裏。 //注意這裏取下標 是用 哈希值 與 桶的長度-1 。 因爲桶的長度是2的n次方,這麼作實際上是等於 一個模運算。可是效率更高 newTab[e.hash & (newCap - 1)] = e; //若是發生過哈希碰撞 ,並且是節點數超過8個,轉化成了紅黑樹(暫且不談 避免過於複雜, 後續專門研究一下紅黑樹) else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //若是發生過哈希碰撞,節點數小於8個。則要根據鏈表上每一個節點的哈希值,依次放入新哈希桶對應下標位置。 else { // preserve order //由於擴容是容量翻倍,因此原鏈表上的每一個節點,如今可能存放在原來的下標,即low位, 或者擴容後的下標,即high位。 high位= low位+原哈希桶容量 //低位鏈表的頭結點、尾節點 Node<K,V> loHead = null, loTail = null; //高位鏈表的頭節點、尾節點 Node<K,V> hiHead = null, hiTail = null; Node<K,V> next;//臨時節點 存放e的下一個節點 do { next = e.next; //這裏又是一個利用位運算 代替常規運算的高效點: 利用哈希值 與 舊的容量,結果只有兩種 0或者oldCap,結果是0則存放在低位,不然存放在高位 if ((e.hash & oldCap) == 0) { //給頭尾節點指針賦值 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; }//高位也是相同的邏輯 else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; }//循環直到鏈表結束 } while ((e = next) != null); //將低位鏈表存放在原index處, if (loTail != null) { loTail.next = null; newTab[j] = loHead; } //將高位鏈表存放在新index處 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
遍歷app
/** * 遍歷 主要看方法nextNode() */ final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } } final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K, V>> { public final Map.Entry<K, V> next() { return nextNode(); } } abstract class HashIterator { Node<K, V> next; // next entry to return Node<K, V> current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node<K, V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do { } while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K, V> nextNode() { Node<K, V>[] t; Node<K, V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do { } while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K, V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }
一、tableSizeFor(int cap)保證數組容量是2的次冪函數
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的次冪-1(即table.length- 1)獲得是數用二進制表示每一位都是1。源碼分析
三、將e.hash放進table數組中,須要e.hash%(table.length- 1)獲得下標;post
這裏用e.hash&(table.length- 1)代替e.hash%(table.length- 1),位運算代替除法;學習
e.hash&(table.length- 1)相似Integer類的toUnsignedLong() 方法:((long) x) & 0xffffffffL,只保留低位;
四、由於e.hash&(table.length- 1)時,比(table.length- 1)高的位都成0了,只用到了e.hash的低位;
e.hash = (h = key.hashCode()) ^ (h >>> 16),使key的hashCode值高16位不變,低16位 由(高16位)^(低16位)獲得;
e.hash&(table.length- 1)時用到的e.hash的低位也有高16位參與進來,減小了衝突碰撞。
舉例可參考:HashMap的hash()