平時一直再用hashmap並無稍微深刻的去了解它,本身花點時間想往裏面在深刻一點,發現它比arraylist難理解不少。html
數據結構中有數組和鏈表來實現對數據的存儲,但這二者基本上是兩個極端。java
數組:數組存儲區間是連續的,佔用內存嚴重,故空間複雜的很大。但數組的二分查找時間複雜度小,爲O(1);數組的特色是:尋址容易,插入和刪除困難;算法
鏈表:鏈表存儲區間離散,佔用內存比較寬鬆,故空間複雜度很小,但時間複雜度很大,達O(N)。鏈表的特色是:尋址困難,插入和刪除容易。 數組
HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。看下面圖;來理解:數據結構
從上圖中能夠看出,HashMap底層就是一個數組結構,只數組中的每一項又是一個鏈表。當新建一個HashMap的時候,就會初始化一個數組。app
transient Entry[] table; static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; …… }
能夠看出,Entry就是數組中的元素,每一個 Map.Entry 其實就是一個key-value對,它持有一個指向下一個元素的引用,這就構成了鏈表。性能
存儲this
public V put(K key, V value) { // HashMap容許存放null鍵和null值。 // 當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。 if (key == null) return putForNullKey(value); // 根據key的keyCode從新計算hash值。 int hash = hash(key.hashCode()); // 搜索指定hash值在對應table中的索引。 int i = indexFor(hash, table.length); // 若是 i 索引處的 Entry 不爲 null,經過循環不斷遍歷 e 元素的下一個元素。 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若是i索引處的Entry爲null,代表此處尚未Entry。 modCount++; // 將key、value添加到i索引處。 addEntry(hash, key, value, i); return null; }
從上面的源代碼中能夠看出:當咱們往HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標), 若是數組該位置上已經存放有其餘元素了,那spa
麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放在鏈尾。若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。.net
ddEntry(hash, key, value, i)方法根據計算出的hash值,將key-value對放在數組table的i索引處。addEntry 是 HashMap 提供的一個包訪問權限的方法,代碼以下:
void addEntry(int hash, K key, V value, int bucketIndex) { // 獲取指定 bucketIndex 索引處的 Entry Entry<K,V> e = table[bucketIndex]; // 將新建立的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 若是 Map 中的 key-value 對的數量超過了極限 if (size++ >= threshold) // 把 table 對象的長度擴充到原來的2倍。 resize(2 * table.length); }
當系統決定存儲HashMap中的key-value對時,徹底沒有考慮Entry中的value,僅僅只是根據key來計算並決定每一個Entry的存儲位置。咱們徹底能夠把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的
存儲位置以後,value 隨之保存在那裏便可。
讀取
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
有了上面存儲時的hash算法做爲基礎,理解起來這段代碼就很容易了。從上面的源代碼中能夠看出:
從HashMap中get元素時,首先計算key的hashCode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。
概括
1)hashMap的key容許爲null,當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。
2)判斷是否key值惟一的標準,是經過對key值的hashCode計算得出,經過經過key獲取value值時也是計算key的hashCode的值去找value值。
3)HashMap 在底層將 key-value 當成一個總體進行處理,這個總體就是一個 Entry 對象。HashMap 底層採用一個 Entry[] 數組來保存全部的 key-value 對,當須要存儲一個 Entry 對象時,會根據hash算法來決定
其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
HashMap有兩個參數影響其性能:初始容量和加載因子。默認初始容量是16,加載因子是0.75。容量是哈希表中桶(Entry數組)的數量,初始容量只是哈希表在建立時的容量。
加載因子是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,經過調用 rehash 方法將容量翻倍。
HashMap中定義的成員變量以下:
static final int DEFAULT_INITIAL_CAPACITY = 16;// 默認初始容量爲16,必須爲2的冪 static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量爲2的30次方 static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默認加載因子0.75 transient Entry<K,V>[] table;// Entry數組,哈希表,長度必須爲2的冪 transient int size;// 已存元素的個數 int threshold;// 下次擴容的臨界值,size>=threshold就會擴容 final float loadFactor;// 加載因子
HashMap一共重載了4個構造方法,分別爲:
HashMap()
構造一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity)
構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor)
構造一個帶指定初始容量和加載因子的空 HashMap。
HashMap(Map<? extendsK,? extendsV> m)
構造一個映射關係與指定 Map 相同的 HashMap。
看一下第三個構造方法源碼,其它構造方法最終調用的都是它。
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); // Find a power of 2 >= initialCapacity // 這裏須要注意一下 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; // 設置加載因子 this.loadFactor = loadFactor; // 設置下次擴容臨界值 threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // 初始化哈希表 table = new Entry[capacity]; useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); init(); }
三、 Java容器(四):HashMap(Java 7)的實現原理
水滴石穿,成功的速度必定要超過父母老去的速度! 少尉【5】