<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                **Java源碼之HashMap** 轉載請注意出處:[http://blog.csdn.net/itismelzp/article/details/50525647](http://blog.csdn.net/itismelzp/article/details/50525647) # 一、HashMap概述 HashMap基于哈希表的?Map?接口的實現。此實現提供所有可選的映射操作,并允許使用?null?值和?null?鍵。(除了不同步和允許使用?null?之外,HashMap?類與?Hashtable?大致相同。)此類不保證映射的順序,特別是它不保證該順序恒久不變。 值得注意的是HashMap不是線程安全的,如果想要線程安全的HashMap,可以通過Collections類的靜態方法synchronizedMap獲得線程安全的HashMap。 ~~~ Map map = Collections.synchronizedMap(new HashMap()); ~~~ # 二、HashMap中的數據結構 ### 1.jdk1.8之前 在jdk1.8之前的HashMap是基于**數組+鏈表**來實現,即嚴蔚敏版《數據結構》中**哈希表(散列表)**鏈地址法,哈希表的優點是查詢速度快。 HashMap中主要是通過key的hashCode來計算hash值,只要hashCode相同,計算出來的hash值就一樣。如果存儲的對象多了,就有可能不同的對象映射到相同的hash值,這就是所謂的hash沖突。HashMap中所用解決hash沖突的方法是鏈地址法。 可參考嚴蔚敏版《數據結構》哈希表解決hash沖突的**鏈地址法**。 ![](https://box.kancloud.cn/2016-02-24_56cd26a207584.jpg) 圖中,黃色部分即代表哈希表,也稱為哈希數組,數組的每個元素都是一個單鏈表的頭節點,鏈表是用來解決沖突的,如果不同的key映射到了數組的同一位置處,就將其放入單鏈表中。 ### 2.jdk1.8中HashMap的實現方式 jdk1.8中對HashMap做的很大的改進,采用**數組+鏈表+紅黑樹**實現,當鏈表長度超過閾值(8)時,將鏈表轉換為紅黑樹,大大減少了hash沖突時查找時間(從原來的O(n)->O(logn))。由于紅黑樹的結點空間是鏈表空間的2倍,為了節省空間,當鏈表長度減少(如刪除操作)到閾值(6)時,又會轉換為鏈表形式。 鏈表中的結點對應HashMap中的Node類(jdk1.8之前用的是Entry類,原理差不多),具體如下: ~~~ // Node是單向鏈表,實現了Map.Entry接口 static class Node<K,V> implements Map.Entry<K,V> { final int hash; // hash值 final K key; // 鍵 V value; // 值 Node<K,V> next; // 指向下一個結點 /* * 構造函數 * 利用(hash值、鍵、值、下一個結點)來構造結點 */ 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 V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final String toString() { return key + "=" + value; } // 實現hashCode() public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } // 判斷兩個結點是否相等 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; } } ~~~ HashMap其實就是一個Node數組,Node對象中包含了鍵和值,其中next也是一個Node對象,它用來處理hash沖突, 使具有相同hash值的結點連在一個鏈表或樹中。 下面是紅黑樹結點: 它繼承自LinkedListMap.Entry,這是一種雙鏈表結點(具體可參考[【Java源碼之LinkedHashMap】](http://blog.csdn.net/itismelzp/article/details/50554412))。 ~~~ /** * 紅黑樹結點,繼承自LinkedHashMap.Entry */ 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); } /** * 返回當前節點所在樹的樹結點 */ final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } } ~~~ # 三、HashMap源碼 ### 1.頭文件 ~~~ package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; ~~~ ### 2.繼承情況 ~~~ public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable ~~~ ### 3.屬性 ~~~ /** * 默認初始容量 - 必須是2的冪次方 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16 /** * 最大容量 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 當構造函數不指定時,默認(Hash表)裝載因子。 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 鏈表->紅黑樹的閾值 */ static final int TREEIFY_THRESHOLD = 8; /** * 紅黑樹->鏈表的閾值 */ 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; ~~~ ~~~ /** * 存放元素的Node數組 */ transient Node<K,V>[] table; /** * 裝Map用Set集合(可用于迭代Map) */ transient Set<Map.Entry<K,V>> entrySet; /** * map中的包含的元素個數 */ transient int size; /** * HashMap的修改次數 */ transient int modCount; // fail-fast機制,下面有解釋 /** * 閾值 - 當實際大小超過臨界值時,會進行擴容。threshold = capacity * loadFactor(注意這里的capacity與size的區別) */ int threshold; // 默認情況下是12 /** * 裝載因子,表示Hsah表中元素的填滿的程度 */ final float loadFactor; ~~~ fail-fast機制:即快速失敗機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。 例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了; 那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。 ### 4.構造函數(4個) ~~~ /** * 構造函數一:指定初始容量和裝載因子 */ 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); // tableSizeFor方法會將initialCapacity轉化成2的冪次方,詳見tableSizeFor方法 } /** * 構造函數二:指定初始容量并使用默認裝載因子(0.75) */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 構造函數三:使用默認初始容量(16)和默認裝載因子(0.75) */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * 構造函數四:使用另一個Map來構造 */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } ~~~ ### 5.常用方法 ### **(1)hash方法** 嚴版《數據結構》中提到的哈希函數的構造方法有: - 直接定址法 - 數字分析法 - 平方取中法 - 折疊法 - 除留取余法 在**Hashtable**中用的是?**除留取余法**, 即便于計算,又能減少沖突。 ~~~ index = (hash & 0x7FFFFFFF) % tab.length; ~~~ 但是取模中的除法運算效率很低,HashMap則通過h & (length - 1)替代取模,得到所在數組位置,這樣效率會高很多。 ~~~ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ~~~ 代碼中,首先由key值通過hashCode()方法獲取h值,再通過h & (length - 1)來得到所在數組的位置。 在HashMap實現中還可以看到如下代碼取代了jdk1.8以前用while循環來保證哈希表的容量一直是2的整數倍數,用移位操作取代了循環移位。 ~~~ /** * 根據給定的容量cap來構造符合2的次冪的值 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; // ">>>"為右移填0操作,即不管符號位是什么都用0填充 n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } ~~~ 可以從源碼看出,在HashMap的構造函數中,都直接或間接的調用了tableSizeFor函數。 下面分析原因:length為2的整數冪保證了length-1最后一位(當然是二進制表示)為**1**,從而保證了取索引操作 h & (length - 1)的最后一位同時有為0和為1的可能性,保證了散列的均勻性。反過來,如果length為奇數時,length-1最后一位為**0**,這樣與h按位“與”的最后一位肯定為0,即索引位置肯定是偶數,這樣數組的奇數位置全部沒有放置元素,浪費了大量空間。 ### (2)數據讀取:get和getNode方法 ~~~ /** * 根據key返回對應的value值 */ public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } /** * 實現Map.get和相關方法 */ final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 使用hash & (length-1)得到所在位置 if (first.hash == hash && // 判斷頭結點 ((k = first.key) == key || (key != null && key.equals(k)))) return first; // 搜索“沖突”鏈表或紅黑樹 if ((e = first.next) != null) { if (first instanceof TreeNode) // 紅黑樹情況 return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { // 鏈表情況 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; } ~~~ ### (3)存儲數據:put和putValue方法 ~~~ /** * 實現Map.put和相關方法 * 參數hash:key的hash值 * 參數key:要設置的key值 * 參數value:要設置的value值 * 參數onlyIfAbsent if true, don't change existing value * 如果為假,則替換原來的value * 參數evict:if false, the table is in creation mode. * 返回:替換時返回oldValue,非替換時返回null */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) // 如果tab為空,則調用resize分配內存 n = (tab = resize()).length; // 使用hash & (lengt-1)得到存入位置,得到插入位置中的結點p if ((p = tab[i = (n - 1) & hash]) == null) // 結點p為null,直接插入 tab[i] = newNode(hash, key, value, null); else { // 插入位置沖突 Node<K,V> e; K k; // 與第一個結點相同:hash值與key值相同(1) if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 與第一個結點不相同 else if (p instanceof TreeNode) // 紅黑樹情況 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 鏈表情況 for (int binCount = 0; ; ++binCount) { // p從表頭依次后移 if ((e = p.next) == null) { // 到達鏈尾 p.next = newNode(hash, key, value, null); // 接入鏈尾 if (binCount >= TREEIFY_THRESHOLD - 1) // 達到(鏈->樹)閾值 treeifyBin(tab, hash); break; } // 找到"相同"對象:hash值與key值相同(2) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; // p后移:p=p.next } } // 處理上述兩處hash值與key值相同 if (e != null) { // 已有key值 V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) // 如果size>threshold時進行擴容,見后面的reise()函數 resize(); afterNodeInsertion(evict); return null; } ~~~ ### (4)擴容策略:resize方法 當**size >?threshold**時調用resize()擴容。 ~~~ /** * 初始化或加倍容量大小。 * * 返回新的hash table數組 */ final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { // 超過最大容量,無法擴容,只能改變閾值 threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && // 容量加倍 oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // 閾值加倍 } else if (oldThr > 0) // 用閾值初始值新的容量 newCap = oldThr; else { // 當閾值==0時 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 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]; // 下面是將舊tab中的Node轉移到新tab中,分鏈表和紅黑樹兩種情況 table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) // 紅黑樹情況 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // 鏈表情況 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; 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); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看