首先想說的是關於HashMap源碼的分析園子裏面應該有不少,而且都是分析得很不錯的文章,可是我仍是想寫出本身的學習總結,以便加深本身的理解,所以就有了此文,另外由於小孩過來了,所以更新速度可能放緩了,(#^.^#)html
1、HashMap的簡單使用java
學習任何一個集合,首先最基本的是學會使用,所以首先咱們看下如何使用HashMap,以及咱們常用的方法又有哪些,代碼以下:面試
package study.collection; import java.util.HashMap; import java.util.Map; public class TestMap { public static void main(String[] args) { Map map = new HashMap(); map.put("張三", new Wife("六亦菲")); map.put("李四",new Wife("楊咪")); map.put("王五",new Wife("趙麗英")); System.out.println(map.get("張三")); System.out.println(map.containsKey("王五")); map.remove("李四"); System.out.println(map.size()); } } class Wife { private String name; public Wife() { } public Wife(String name) { super(); this.name = name; } @Override public String toString() { return "Wife [name=" + name + "]"; } }
上面主要演示了hashmap 中的幾個主要方法,如put() get() remove() size() 等方法,實際map中還有不少的方法,以下:算法
說明:圓形 -- 對外提供的public 方法 正方形 ---內部使用的private 方法 三角形 --- static 方法。數組
2、HashMap的概述安全
上面咱們介紹了HashMap的基本使用方法,下面咱們進一步來分析下HashMap,而不在僅僅在於會使用。數據結構
2.1 HashMap的定義app
HashMap基於哈希表的 Map 接口的實現。此實現提供全部可選的映射操做,並容許使用 null 值和 null 鍵。(除了不一樣步和容許使用 null 以外,HashMap 類與 Hashtable 大體相同.)此類不保證映射的順序,特別是它不保證該順序恆久不變。值得注意的是HashMap不是線程安全的,若是想要線程安全的HashMap,能夠經過Collections類的靜態方法synchronizedMap得到線程安全的HashMap。dom
Map map = Collections.synchronizedMap(new HashMap());
咱們來看下Map的類定義:ide
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
從上面的HashMap 此類的定義來看,我能夠這樣來理解HashMap; 它實現了Map接口,繼承了AbstractMap,其中Map接口定義了鍵映射到值得規則,而AbstractMap類提供了Map接口中主要的實現,是爲了最大限度的減小實際開發人員在實現HashMap所須要的工做。
2.2 HashMap的構造函數
HashMap提供了四個構造函數:
HashMap():構造一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):構造一個帶指定初始容量和加載因子的空 HashMap。
HashMap(Map<? extends K,? extends V> m) :構造一個映射關係與指定 Map 相同的新 HashMap。
在這裏提到了兩個參數:初始容量,加載因子。這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中桶的數量,初始容量是建立哈希表時的容量,加載因子是哈希表在其容量擴容以前能夠達到多滿的一種尺度【閥值=加載因子*容量】,加載因子它衡量的是一個散列表的空間的使用程度,加載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是O(1+a),所以若是加載因子越大,對空間的利用更充分,然然後果是查找效率的下降;若是加載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。系統默認加載因子爲0.75,通常狀況下咱們是無需修改的。
HashMap是一種支持快速存取的數據結構,要了解它的性能必需要了解它的數據結構。
2.3 HashMap的數據結構
咱們知道在Java中最經常使用的兩種結構是數組和模擬指針(引用),幾乎全部的數據結構均可以利用這兩種來組合實現,HashMap也是如此,其底層爲數組+鏈表實現;若是採用數組那麼必然就涉及到數組的索引的問題,那麼這個索引是什麼呢?就是經過key的hashcode計算出來的,採用數組的好處就在於能夠快速的訪問,能夠回憶下List集合中ArrayList.就是典型的底層爲數組,因此訪問的速度很快,可是若是單純的採用數組,那麼爲何hashmap 又有鏈表結構呢?這個是由於數組的缺陷是插入元素和刪除元素比較慢,而鏈表具有的就是插入和刪除快的特色,所以爲告終合這兩種特性因而就採用了數組加鏈表的結構進行。咱們看下源碼就能夠看出:
public HashMap(int initialCapacity, float loadFactor) { //初始容量不能<0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量不能 > 最大容量值,HashMap的最大容量值爲2^30 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //負載因子不能 < 0 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 計算出大於 initialCapacity 的最小的 2 的 n 次方值。 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; //設置HashMap的容量極限,當HashMap的容量達到該極限時就會進行擴容操做 threshold = (int) (capacity * loadFactor); //初始化table數組 table = new Entry[capacity]; init(); }
從源碼中能夠看出,每次新建一個HashMap時,都會初始化一個table數組。table數組的元素爲Entry節點。
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ....... }
其中Entry爲HashMap的內部類,它包含了鍵key、值value、下一個節點next,以及hash值,這是很是重要的,正是因爲Entry才構成了table數組的項爲鏈表。
總結:
HashMap的底層主要是基於數組和鏈表來實現的,它之因此有至關快的查詢速度主要是由於它是經過計算散列碼來決定存儲的位置。HashMap中主要是經過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就同樣。若是存儲的對象對多了,就有可能不一樣的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。學過數據結構的同窗都知道,解決hash衝突的方法有不少,HashMap底層是經過鏈表來解決hash衝突的。
圖中,0~15部分即表明哈希表,也稱爲哈希數組,數組的每一個元素都是一個單鏈表的頭節點,鏈表是用來解決衝突的,若是不一樣的key映射到了數組的同一位置處,就將其放入單鏈表中。
從上圖咱們能夠發現哈希表是由數組+鏈表組成的,一個長度爲16的數組中,每一個元素存儲的是一個鏈表的頭結點Bucket桶。那麼這些元素是按照什麼樣的規則存儲到數組中呢。通常狀況是經過hash(key)%len得到,也就是元素的key的哈希值對數組長度取模獲得。好比上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。因此十二、2八、108以及140都存儲在數組下標爲12的位置。
HashMap其實也是一個線性的數組實現的,因此能夠理解爲其存儲數據的容器就是一個線性數組。這可能讓咱們很不解,一個線性的數組怎麼實現按鍵值對來存取數據呢?這裏HashMap有作一些處理。
HashMap裏面實現一個靜態內部類Entry【能夠看上面的源碼】,其重要的屬性有 key , value, next【單鏈表】,從屬性key,value咱們就能很明顯的看出來Entry就是HashMap鍵值對實現的一個基礎bean,咱們上面說到HashMap的基礎就是一個線性數組,這個數組就是Entry[],Map裏面的內容都保存在Entry[]裏面。即HashMap其實就是一個Entry數組,Entry對象中包含了鍵和值,其中next也是一個Entry對象,它就是用來處理hash衝突的,造成一個鏈表。
說明:鏈表結構中若是存放的數據太多,JDK1.8會使用紅黑樹存儲來提升查找(get)性能.
3、HashMap的源碼解析
咱們先重點分析HashMap的兩個重要方法:put 和 get方法,也是咱們最常使用的方法:
public V put(K key, V value) { // 若「key爲null」,則將該鍵值對添加到table[0]中。 if (key == null) return putForNullKey(value); // 若「key不爲null」,則計算該key的哈希值,而後將其添加到該哈希值對應的鏈表中。 int hash = hash(key.hashCode()); //搜索指定hash值在對應table中的索引 int i = indexFor(hash, table.length); // 循環遍歷Entry數組,若「該key」對應的鍵值對已經存在,則用新的value取代舊的value。而後退出! 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))) { //若是key相同則覆蓋並返回舊值 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //修改次數+1 modCount++; //將key-value添加到table[i]處 addEntry(hash, key, value, i); return null; }
上面程序中用到了一個重要的內部接口:Map.Entry,每一個 Map.Entry 其實就是一個 key-value 對。從上面程序中能夠看出:當系統決定存儲 HashMap 中的 key-value 對時,徹底沒有考慮 Entry 中的 value,僅僅只是根據 key 來計算並決定每一個 Entry 的存儲位置。這也說明了前面的結論:咱們徹底能夠把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的存儲位置以後,value 隨之保存在那裏便可。
咱們慢慢的來分析這個函數,第2和3行的做用就是處理key值爲null的狀況,咱們看看putForNullKey(value)方法:
private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { //若是有key爲null的對象存在,則覆蓋掉 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); //若是鍵爲null的話,則hash值爲0 return null; }
注意:若是key爲null的話,hash值爲0,對象存儲在數組中索引爲0的位置。即table[0]
咱們再回去看看put方法中第4行,它是經過key的hashCode值計算hash碼,下面是計算hash碼的函數:
//計算hash值的方法 經過鍵的hashCode來計算 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
獲得hash碼以後就會經過hash碼去計算出應該存儲在數組中的索引,計算索引的函數以下:
static int indexFor(int h, int length) { //根據hash值和數組長度算出索引值 return h & (length-1); //這裏不能隨便算取,用hash&(length-1)是有緣由的,這樣能夠確保算出來的索引是在數組大小範圍內,不會超出 }
這個咱們要重點說下,咱們通常對哈希表的散列很天然地會想到用hash值對length取模(即除法散列法),Hashtable中也是這樣實現的,這種方法基本能保證元素在哈希表中散列的比較均勻,但取模會用到除法運算,效率很低,HashMap中則經過h&(length-1)的方法來代替取模。
h&(length - 1),這句話除了上面的取模運算外還有一個很是重要的責任:均勻分佈table數據和充分利用空間。
接下來,咱們分析下爲何哈希表的容量必定要是2的整數次冪。首先,length爲2的整數次冪的話,h&(length-1)就至關於對length取模,這樣便保證了散列的均勻,同時也提高了效率;其次,length爲2的整數次冪的話,爲偶數,這樣length-1爲奇數,奇數的最後一位是1,這樣便保證了h&(length-1)的最後一位可能爲0,也可能爲1(這取決於h的值),即與後的結果可能爲偶數,也可能爲奇數,這樣即可以保證散列的均勻性,而若是length爲奇數的話,很明顯length-1爲偶數,它的最後一位是0,這樣h&(length-1)的最後一位確定爲0,即只能爲偶數,這樣任何hash值都只會被散列到數組的偶數下標位置上,這便浪費了近一半的空間,所以,length取2的整數次冪,是爲了使不一樣hash值發生碰撞的機率較小,這樣就能使元素在哈希表中均勻地散列。
舉例:這裏咱們假設length爲16(2^n)和15,h爲五、六、7。
當length=15時,6和7的結果同樣,這樣表示他們在table存儲的位置是相同的,也就是產生了碰撞,六、7就會在一個位置造成鏈表,這樣就會致使查詢速度下降。誠然這裏只分析三個數字不是不少,那麼咱們就看0-15。
從上面的圖表中咱們看到總共發生了8此碰撞,同時發現浪費的空間很是大,有一、三、五、七、九、十一、1三、15處沒有記錄,也就是沒有存放數據。這是由於他們在與14進行&運算時,獲得的結果最後一位永遠都是0,即000一、00十一、010一、01十一、100一、10十一、110一、1111位置處是不可能存儲數據的,空間減小,進一步增長碰撞概率,這樣就會致使查詢速度慢。而當length = 16時,length – 1 = 15 即1111,那麼進行低位&運算時,值老是與原來hash值相同,而進行高位運算時,其值等於其低位值。因此說當length = 2^n時,不一樣的hash值發生碰撞的機率比較小,這樣就會使得數據在table數組中分佈較均勻,查詢速度也較快。
根據上面 put 方法的源代碼能夠看出,當程序試圖將一個key-value對放入HashMap中時,程序首先根據該 key 的 hashCode() 返回值決定該 Entry 的存儲位置:
具體說明繼續看 addEntry() 方法的說明。
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //若是要加入的位置有值,將該位置原先的值設置爲新entry的next,也就是新entry鏈表的下一個節點 table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) //若是大於臨界值就擴容 resize(2 * table.length); //以2的倍數擴容 }
參數bucketIndex就是indexFor函數計算出來的索引值,第2行代碼是取得數組中索引爲bucketIndex的Entry對象,第3行就是用hash、key、value構建一個新的Entry對象放到索引爲bucketIndex的位置,而且將該位置原先的對象設置爲新對象的next構成鏈表。
第4行和第5行就是判斷put後size是否達到了臨界值threshold,若是達到了臨界值就要進行擴容,HashMap擴容是擴爲原來的兩倍。
resize()方法以下:
從新調整HashMap的大小,newCapacity是調整後的單位
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable);//用來將原先table的元素所有移到newTable裏面 table = newTable; //再將newTable賦值給table threshold = (int)(newCapacity * loadFactor);//從新計算臨界值 }
新建了一個HashMap的底層數組,上面代碼中第10行爲調用transfer方法,將HashMap的所有元素添加到新的HashMap中,並從新計算元素在新的數組中的索引位置
當HashMap中的元素愈來愈多的時候,hash衝突的概率也就愈來愈高,由於數組的長度是固定的。因此爲了提升查詢的效率,就要對HashMap的數組進行擴容,數組擴容這個操做也會出如今ArrayList中,這是一個經常使用的操做,而在HashMap數組擴容以後,最消耗性能的點就出現了:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize
那麼HashMap何時進行擴容呢?當HashMap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認狀況下,數組大小爲16,那麼當HashMap中元素個數超過16*0.75=12的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,擴容是須要進行數組複製的,複製數組是很是消耗性能的操做,因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。
上面講完了put的方法,接下來咱們看下get方法:
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方法在對應位置的鏈表中找到須要的元素。
概括起來簡單地說,HashMap 在底層將 key-value 當成一個總體進行處理,這個總體就是一個 Entry 對象。HashMap 底層採用一個 Entry[] 數組來保存全部的 key-value 對,當須要存儲一個 Entry 對象時,會根據hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Entry。
4、HashMap重要知識點
1.性能參數:
HashMap 包含以下幾個構造器:
HashMap():構建一個初始容量爲 16,負載因子爲 0.75 的 HashMap。
HashMap(int initialCapacity):構建一個初始容量爲 initialCapacity,負載因子爲 0.75 的 HashMap。
HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負載因子建立一個 HashMap。
HashMap的基礎構造器HashMap(int initialCapacity, float loadFactor)帶有兩個參數,它們是初始容量initialCapacity和加載因子loadFactor。
initialCapacity:HashMap的最大容量,即爲底層數組的長度。
loadFactor:負載因子loadFactor定義爲:散列表的實際元素數目(n)/ 散列表的容量(m)。 負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來講,查找一個元素的平均時間是O(1+a),所以若是負載因子越大,對空間的利用更充分,然然後果是查找效率的下降;若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費。
HashMap的實現中,經過threshold字段來判斷HashMap的最大容量:
threshold = (int)(capacity * loadFactor);
結合負載因子的定義公式可知,threshold就是在此loadFactor和capacity對應下容許的最大元素數目,超過這個數目就從新resize,以下降實際的負載因子。默認的的負載因子0.75是對空間和時間效率的一個平衡選擇。當容量超出此最大容量時, resize後的HashMap容量是容量的兩倍:
if (size++ >= threshold) resize(2 * table.length);
2.HashCode的重要性
HashMap中對Key的HashCode要作一次rehash,防止一些糟糕的Hash算法生成的糟糕的HashCode,那麼爲何要防止糟糕的HashCode?
糟糕的HashCode意味着的是Hash衝突,即多個不一樣的Key可能獲得的是同一個HashCode,糟糕的Hash算法意味着的就是Hash衝突的機率增大,這意味着HashMap的性能將降低,表如今兩方面:
一、有10個Key,可能6個Key的HashCode都相同,另外四個Key所在的Entry均勻分佈在table的位置上,而某一個位置上卻鏈接了6個Entry。這就失去了HashMap的意義,HashMap這種數據結構性高性能的前提是,Entry均勻地分佈在table位置上,但如今確是1 1 1 1 6的分佈。因此,咱們要求HashCode有很強的隨機性,這樣就儘量地能夠保證了Entry分佈的隨機性,提高了HashMap的效率。
二、HashMap在一個某個table位置上遍歷鏈表的時候的代碼:
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
看到,因爲採用了"&&"運算符,所以先比較HashCode,HashCode都不相同就直接pass了,不會再進行equals比較了。HashCode由於是int值,比較速度很是快,而equals方法每每會對比一系列的內容,速度會慢一些。Hash衝突的機率大,意味着equals比較的次數勢必增多,必然下降了HashMap的效率了。
3.HashMap的table爲何是transient的
一個很是細節的地方:
transient Entry[] table;
看到table用了transient修飾,也就是說table裏面的內容全都不會被序列化,這麼寫的緣由?
由於HashMap是基於HashCode的,HashCode做爲Object的方法,是native的:
public native int hashCode();
這意味着的是:HashCode和底層實現相關,不一樣的虛擬機可能有不一樣的HashCode算法。再進一步說得明白些就是,可能同一個Key在虛擬機A上的HashCode=1,在虛擬機B上的HashCode=2,在虛擬機C上的HashCode=3。
這就有問題了,Java自誕生以來,就以跨平臺性做爲最大賣點,好了,若是table不被transient修飾,在虛擬機A上能夠用的程序到虛擬機B上能夠用的程序就不能用了,失去了跨平臺性,由於:
一、Key在虛擬機A上的HashCode=100,連在table[4]上
二、Key在虛擬機B上的HashCode=101,這樣,就去table[5]上找Key,明顯找不到
整個代碼就出問題了。所以,爲了不這一點,Java採起了重寫本身序列化table的方法,在writeObject選擇將key和value追加到序列化的文件最後面:
private void writeObject(java.io.ObjectOutputStream s) throws IOException { Iterator<Map.Entry<K,V>> i = (size > 0) ? entrySet0().iterator() : null; // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); // Write out number of buckets s.writeInt(table.length); // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) if (i != null) { while (i.hasNext()) { Map.Entry<K,V> e = i.next(); s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } }
而在readObject的時候重構HashMap數據結構:
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold, loadfactor, and any hidden stuff s.defaultReadObject(); // Read in number of buckets and allocate the bucket array; int numBuckets = s.readInt(); table = new Entry[numBuckets]; init(); // Give subclass a chance to do its thing. // Read in size (number of Mappings) int size = s.readInt(); // Read the keys and values, and put the mappings in the HashMap for (int i=0; i<size; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putForCreate(key, value); } }
一種麻煩的方式,但卻保證了跨平臺性。
這個例子也告訴了咱們:儘管使用的虛擬機大多數狀況下都是HotSpot,可是也不能對其它虛擬機無論不顧,有跨平臺的思想是一件好事。
4.HashMap和Hashtable的區別
HashMap和Hashtable是一組類似的鍵值對集合,它們的區別也是面試常被問的問題之一,我這裏簡單總結一下HashMap和Hashtable的區別:
一、Hashtable是線程安全的,Hashtable全部對外提供的方法都使用了synchronized,也就是同步,而HashMap則是線程非安全的
二、Hashtable不容許空的value,空的value將致使空指針異常,而HashMap則無所謂,沒有這方面的限制
三、上面兩個缺點是最主要的區別,另一個區別可有可無,我只是提一下,就是兩個的rehash算法不一樣,Hashtable的是:
private int hash(Object k) { // hashSeed will be zero if alternative hashing is disabled. return hashSeed ^ k.hashCode(); }
這個hashSeed是使用sun.misc.Hashing類的randomHashSeed方法產生的。HashMap的rehash算法上面看過了,也就是:
static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
5、本身動手寫一個簡單的HashMap
咱們來動手實現一個簡單的hashmap
package study.collection; public class MyMap001 { /** * 定義一個數組來存放全部的key 和 Value 的值,這裏先假設數組的大小爲很大的一個.實際底層爲數組加鏈表 數組存在擴容的場景 * 擴容在前面MyArrayList 中講過了,因此這裏就不重複去作了,所以設置爲了990大小. */ MyEntry[] arr = new MyEntry[990]; /** * 大小 */ int size; public void put(Object key,Object value){ MyEntry e = new MyEntry(key,value); //解決鍵值重複的處理 for(int i=0;i<size;i++){ if(arr[i].key.equals(key)){ arr[i].value=value; return ; } } arr[size++] = e; } public Object get(Object key){ /** * 遍歷尋找 */ for(int i=0;i<size;i++){ if(arr[i].key.equals(key)){ return arr[i].value; } } return null; } /** * 遍歷尋找 * @param key * @return */ public boolean containsKey(Object key){ for(int i=0;i<size;i++){ if(arr[i].key.equals(key)){ return true; } } return false; } public boolean containsValue(Object value){ for(int i=0;i<size;i++){ if(arr[i].value.equals(value)){ return true; } } return false; } public static void main(String[] args) { MyMap001 m = new MyMap001(); m.put("張三", new Wife("楊洋")); m.put("李四", new Wife("王五")); Wife w = (Wife) m.get("張三"); System.out.println(w); } } /** * * Map 中存放的一個key 一個Value * */ class MyEntry { Object key; Object value; public MyEntry(Object key, Object value) { super(); this.key = key; this.value = value; } }
上面的缺陷爲:查詢效率低,遍歷元素的過程。由於全部的元素都存在數組中,採用的數組方式,沒有結合鏈表。
改進;結合鏈表機制。
package study.collection; import java.util.LinkedList; public class MyMap002 { LinkedList[] arr = new LinkedList[9]; //Map的底層結構就是:數組+鏈表! 暫時不關注擴容 int size; public void put(Object key,Object value){ MyEntry e = new MyEntry(key,value); //計算hash值 int hash = key.hashCode(); //防止存在負數 hash = hash<0?-hash:hash; //取模 int a = hash%arr.length; if(arr[a]==null) { //此索引處沒有元素 LinkedList list = new LinkedList(); list.add(e); arr[a] = list; }else { //若是存在元素,則先取出 LinkedList list = arr[a]; //遍歷此節點上面的鏈表是否重複元素 for(int i=0;i<list.size();i++) { MyEntry e2 = (MyEntry) list.get(i); //是否重複 if(e2.key.equals(key)){ e2.value = value; //鍵值重複直接覆蓋! return; } } //鏈表節點上面追加元素 arr[a].add(e); } //a:1000-->1 b:10000-->13 } public Object get(Object key){ int a = key.hashCode()%arr.length; if(arr[a]!=null){ LinkedList list = arr[a]; for(int i=0;i<list.size();i++){ MyEntry e = (MyEntry) list.get(i); if(e.key.equals(key)){ return e.value; } } } return null; } public static void main(String[] args) { MyMap002 m = new MyMap002(); m.put("張三", new Wife("楊洋")); m.put("李四", new Wife("王五")); Wife w = (Wife) m.get("張三"); System.out.println(w); } }
上面是一個簡單的實現優化,也只是實現了最基本的功能,但重點在於理解原理和hashmap的原理
6、HashMap源碼註釋
package java.util; import java.io.*; public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // 默認的初始容量(容量爲HashMap中槽的數目)是16,且實際容量必須是2的整數次冪。 static final int DEFAULT_INITIAL_CAPACITY = 16; // 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換) static final int MAXIMUM_CAPACITY = 1 << 30; // 默認加載因子爲0.75 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 存儲數據的Entry數組,長度是2的冪。 // HashMap採用鏈表法解決衝突,每個Entry本質上是一個單向鏈表 transient Entry[] table; // HashMap的底層數組中已用槽的數量 transient int size; // HashMap的閾值,用於判斷是否須要調整HashMap的容量(threshold = 容量*加載因子) int threshold; // 加載因子實際大小 final float loadFactor; // HashMap被改變的次數 transient volatile int modCount; // 指定「容量大小」和「加載因子」的構造函數 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // HashMap的最大容量只能是MAXIMUM_CAPACITY if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //加載所以不能小於0 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 找出「大於initialCapacity」的最小的2的冪 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; // 設置「加載因子」 this.loadFactor = loadFactor; // 設置「HashMap閾值」,當HashMap中存儲數據的數量達到threshold時,就須要將HashMap的容量加倍。 threshold = (int)(capacity * loadFactor); // 建立Entry數組,用來保存數據 table = new Entry[capacity]; init(); } // 指定「容量大小」的構造函數 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 默認構造函數。 public HashMap() { // 設置「加載因子」爲默認加載因子0.75 this.loadFactor = DEFAULT_LOAD_FACTOR; // 設置「HashMap閾值」,當HashMap中存儲數據的數量達到threshold時,就須要將HashMap的容量加倍。 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 建立Entry數組,用來保存數據 table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } // 包含「子Map」的構造函數 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 將m中的所有元素逐個添加到HashMap中 putAllForCreate(m); } //求hash值的方法,從新計算hash值 static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } // 返回h在數組中的索引值,這裏用&代替取模,旨在提高效率 // h & (length-1)保證返回值的小於length static int indexFor(int h, int length) { return h & (length-1); } public int size() { return size; } public boolean isEmpty() { return size == 0; } // 獲取key對應的value public V get(Object key) { if (key == null) return getForNullKey(); // 獲取key的hash值 int hash = hash(key.hashCode()); // 在「該hash值對應的鏈表」上查找「鍵值等於key」的元素 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; //判斷key是否相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } //沒找到則返回null return null; } // 獲取「key爲null」的元素的值 // HashMap將「key爲null」的元素存儲在table[0]位置,但不必定是該鏈表的第一個位置! private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } // HashMap是否包含key public boolean containsKey(Object key) { return getEntry(key) != null; } // 返回「鍵爲key」的鍵值對 final Entry<K,V> getEntry(Object key) { // 獲取哈希值 // HashMap將「key爲null」的元素存儲在table[0]位置,「key不爲null」的則調用hash()計算哈希值 int hash = (key == null) ? 0 : hash(key.hashCode()); // 在「該hash值對應的鏈表」上查找「鍵值等於key」的元素 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 != null && key.equals(k)))) return e; } return null; } // 將「key-value」添加到HashMap中 public V put(K key, V value) { // 若「key爲null」,則將該鍵值對添加到table[0]中。 if (key == null) return putForNullKey(value); // 若「key不爲null」,則計算該key的哈希值,而後將其添加到該哈希值對應的鏈表中。 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 若「該key」對應的鍵值對已經存在,則用新的value取代舊的value。而後退出! if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若「該key」對應的鍵值對不存在,則將「key-value」添加到table中 modCount++; //將key-value添加到table[i]處 addEntry(hash, key, value, i); return null; } // putForNullKey()的做用是將「key爲null」鍵值對添加到table[0]位置 private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若是沒有存在key爲null的鍵值對,則直接題阿見到table[0]處! modCount++; addEntry(0, null, value, 0); return null; } // 建立HashMap對應的「添加方法」, // 它和put()不一樣。putForCreate()是內部方法,它被構造函數等調用,用來建立HashMap // 而put()是對外提供的往HashMap中添加元素的方法。 private void putForCreate(K key, V value) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); // 若該HashMap表中存在「鍵值等於key」的元素,則替換該元素的value值 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } // 若該HashMap表中不存在「鍵值等於key」的元素,則將該key-value添加到HashMap中 createEntry(hash, key, value, i); } // 將「m」中的所有元素都添加到HashMap中。 // 該方法被內部的構造HashMap的方法所調用。 private void putAllForCreate(Map<? extends K, ? extends V> m) { // 利用迭代器將元素逐個添加到HashMap中 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry<? extends K, ? extends V> e = i.next(); putForCreate(e.getKey(), e.getValue()); } } // 從新調整HashMap的大小,newCapacity是調整後的容量 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //若是就容量已經達到了最大值,則不能再擴容,直接返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新建一個HashMap,將「舊HashMap」的所有元素添加到「新HashMap」中, // 而後,將「新HashMap」賦值給「舊HashMap」。 Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } // 將HashMap中的所有元素都添加到newTable中 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } // 將"m"的所有元素都添加到HashMap中 public void putAll(Map<? extends K, ? extends V> m) { // 有效性判斷 int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; // 計算容量是否足夠, // 若「當前閥值容量 < 須要的容量」,則將容量x2。 if (numKeysToBeAdded > threshold) { int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } // 經過迭代器,將「m」中的元素逐個添加到HashMap中。 for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry<? extends K, ? extends V> e = i.next(); put(e.getKey(), e.getValue()); } } // 刪除「鍵爲key」元素 public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } // 刪除「鍵爲key」的元素 final Entry<K,V> removeEntryForKey(Object key) { // 獲取哈希值。若key爲null,則哈希值爲0;不然調用hash()進行計算 int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 刪除鏈表中「鍵爲key」的元素 // 本質是「刪除單向鏈表中的節點」 while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } // 刪除「鍵值對」 final Entry<K,V> removeMapping(Object o) { if (!(o instanceof Map.Entry)) return null; Map.Entry<K,V> entry = (Map.Entry<K,V>) o; Object key = entry.getKey(); int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 刪除鏈表中的「鍵值對e」 // 本質是「刪除單向鏈表中的節點」 while (e != null) { Entry<K,V> next = e.next; if (e.hash == hash && e.equals(entry)) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } // 清空HashMap,將全部的元素設爲null public void clear() { modCount++; Entry[] tab = table; for (int i = 0; i < tab.length; i++) tab[i] = null; size = 0; } // 是否包含「值爲value」的元素 public boolean containsValue(Object value) { // 若「value爲null」,則調用containsNullValue()查找 if (value == null) return containsNullValue(); // 若「value不爲null」,則查找HashMap中是否有值爲value的節點。 Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false; } // 是否包含null值 private boolean containsNullValue() { Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (e.value == null) return true; return false; } // 克隆一個HashMap,並返回Object對象 public Object clone() { HashMap<K,V> result = null; try { result = (HashMap<K,V>)super.clone(); } catch (CloneNotSupportedException e) { // assert false; } result.table = new Entry[table.length]; result.entrySet = null; result.modCount = 0; result.size = 0; result.init(); // 調用putAllForCreate()將所有元素添加到HashMap中 result.putAllForCreate(this); return result; } // Entry是單向鏈表。 // 它是 「HashMap鏈式存儲法」對應的鏈表。 // 它實現了Map.Entry 接口,即實現getKey(), getValue(), setValue(V value), equals(Object o), hashCode()這些函數 static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一個節點 Entry<K,V> next; final int hash; // 構造函數。 // 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下一節點(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } 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; } // 判斷兩個Entry是否相等 // 若兩個Entry的「key」和「value」都相等,則返回true。 // 不然,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 實現hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 當向HashMap中添加元素時,繪調用recordAccess()。 // 這裏不作任何處理 void recordAccess(HashMap<K,V> m) { } // 當從HashMap中刪除元素時,繪調用recordRemoval()。 // 這裏不作任何處理 void recordRemoval(HashMap<K,V> m) { } } // 新增Entry。將「key-value」插入指定位置,bucketIndex是位置索引。 void addEntry(int hash, K key, V value, int bucketIndex) { // 保存「bucketIndex」位置的值到「e」中 Entry<K,V> e = table[bucketIndex]; // 設置「bucketIndex」位置的元素爲「新Entry」, // 設置「e」爲「新Entry的下一個節點」 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 若HashMap的實際大小 不小於 「閾值」,則調整HashMap的大小 if (size++ >= threshold) resize(2 * table.length); } // 建立Entry。將「key-value」插入指定位置。 void createEntry(int hash, K key, V value, int bucketIndex) { // 保存「bucketIndex」位置的值到「e」中 Entry<K,V> e = table[bucketIndex]; // 設置「bucketIndex」位置的元素爲「新Entry」, // 設置「e」爲「新Entry的下一個節點」 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); size++; } // HashIterator是HashMap迭代器的抽象出來的父類,實現了公共了函數。 // 它包含「key迭代器(KeyIterator)」、「Value迭代器(ValueIterator)」和「Entry迭代器(EntryIterator)」3個子類。 private abstract class HashIterator<E> implements Iterator<E> { // 下一個元素 Entry<K,V> next; // expectedModCount用於實現fast-fail機制。 int expectedModCount; // 當前索引 int index; // 當前元素 Entry<K,V> current; HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; // 將next指向table中第一個不爲null的元素。 // 這裏利用了index的初始值爲0,從0開始依次向後遍歷,直到找到不爲null的元素就退出循環。 while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } // 獲取下一個元素 final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); // 注意!!! // 一個Entry就是一個單向鏈表 // 若該Entry的下一個節點不爲空,就將next指向下一個節點; // 不然,將next指向下一個鏈表(也是下一個Entry)的不爲null的節點。 if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } // 刪除當前元素 public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } } // value的迭代器 private final class ValueIterator extends HashIterator<V> { public V next() { return nextEntry().value; } } // key的迭代器 private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } // Entry的迭代器 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } } // 返回一個「key迭代器」 Iterator<K> newKeyIterator() { return new KeyIterator(); } // 返回一個「value迭代器」 Iterator<V> newValueIterator() { return new ValueIterator(); } // 返回一個「entry迭代器」 Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); } // HashMap的Entry對應的集合 private transient Set<Map.Entry<K,V>> entrySet = null; // 返回「key的集合」,實際上返回一個「KeySet對象」 public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); } // Key對應的集合 // KeySet繼承於AbstractSet,說明該集合中沒有重複的Key。 private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { return HashMap.this.removeEntryForKey(o) != null; } public void clear() { HashMap.this.clear(); } } // 返回「value集合」,實際上返回的是一個Values對象 public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); } // 「value集合」 // Values繼承於AbstractCollection,不一樣於「KeySet繼承於AbstractSet」, // Values中的元素可以重複。由於不一樣的key能夠指向相同的value。 private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } public int size() { return size; } public boolean contains(Object o) { return containsValue(o); } public void clear() { HashMap.this.clear(); } } // 返回「HashMap的Entry集合」 public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } // 返回「HashMap的Entry集合」,它實際是返回一個EntrySet對象 private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } // EntrySet對應的集合 // EntrySet繼承於AbstractSet,說明該集合中沒有重複的EntrySet。 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } } // java.io.Serializable的寫入函數 // 將HashMap的「總的容量,實際容量,全部的Entry」都寫入到輸出流中 private void writeObject(java.io.ObjectOutputStream s) throws IOException { Iterator<Map.Entry<K,V>> i = (size > 0) ? entrySet0().iterator() : null; // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); // Write out number of buckets s.writeInt(table.length); // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) if (i != null) { while (i.hasNext()) { Map.Entry<K,V> e = i.next(); s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } } private static final long serialVersionUID = 362498820763181265L; // java.io.Serializable的讀取函數:根據寫入方式讀出 // 將HashMap的「總的容量,實際容量,全部的Entry」依次讀出 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold, loadfactor, and any hidden stuff s.defaultReadObject(); // Read in number of buckets and allocate the bucket array; int numBuckets = s.readInt(); table = new Entry[numBuckets]; init(); // Give subclass a chance to do its thing. // Read in size (number of Mappings) int size = s.readInt(); // Read the keys and values, and put the mappings in the HashMap for (int i=0; i<size; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putForCreate(key, value); } } // 返回「HashMap總的容量」 int capacity() { return table.length; } // 返回「HashMap的加載因子」 float loadFactor() { return loadFactor; } }
參考資料:
http://www.cnblogs.com/ITtangtang/p/3948406.htmlhttp://blog.csdn.net/ns_code/article/details/36034955http://www.cnblogs.com/xrq730/p/5030920.htmlhttp://blog.csdn.net/chenssy