public class HashMap<k,v> extends AbstractMap<k,v> implements Map<k,v>, Cloneable, Serializable {
Map并不是繼承自Collection接口
. 繼承:
AbstractMap<k,v>

抽象類中提供了一些實(shí)現(xiàn)的方法,后面的子類中如果沒有特殊的實(shí)現(xiàn)細(xì)節(jié),可以直接使用AbstractMap中的方法。
.實(shí)現(xiàn):
Map<k,v>?

這里實(shí)現(xiàn)的Map接口,java的設(shè)計(jì)中通常既然繼承了AbstractMap,那么就沒有必要實(shí)現(xiàn)Map接口,所以這里在Stack Overfloooow中解釋的意思是為了讓閱讀者明確知道HapMap來自Map
Cloneable
Cloneable這個(gè)接口設(shè)計(jì)時(shí)并沒有clone()方法,所以在我們實(shí)現(xiàn)這個(gè)接口的時(shí)候需要重寫clone()方法,
Serializable
表明HashMap對(duì)象是可以被序列化的。
設(shè)計(jì)理念
HashMap是基于hash表(hashtable)實(shí)現(xiàn)的,hash表又叫關(guān)聯(lián)數(shù)組,所以HashMap的底層是一個(gè)數(shù)組,一種鍵值對(duì)數(shù)據(jù)結(jié)構(gòu),鍵是不可以重復(fù)的,這里說下HashMap鍵值對(duì)的報(bào)存過程,key在經(jīng)過hash函數(shù)作用后會(huì)生成一個(gè)對(duì)應(yīng)值槽的索引,但是在不同的key經(jīng)過hash函數(shù)處理的時(shí)候可能會(huì)的到相同的索引,因此會(huì)產(chǎn)生重復(fù)的情況,因此:
. 設(shè)計(jì)一個(gè)好的hash函數(shù)可以盡可能的減少?zèng)_突的發(fā)生,
.其次是解決如果沖突發(fā)生后怎么解決這個(gè)沖突。
HashMap的特點(diǎn):
☆ 與HashMap對(duì)應(yīng)的是HashTable,相比較而言,HashTable是線程安全的,HashMap是允許鍵值都是null的,但是相反的是HashTable中鍵值都是部允許為null的,
☆HashMap是無序的,并且隨著世間的推移可能會(huì)出現(xiàn)某一個(gè)元素的位置可能會(huì)改變? ? ? (resize) 意思就是重新計(jì)算容量,? 這里涉及到HashMap的擴(kuò)容機(jī)制,既然HashMap的底層是一個(gè)數(shù)組那么可想而知在java中數(shù)組的擴(kuò)容原理:重新建立一個(gè)容量更大的數(shù)組,把原來的數(shù)組copy到新的數(shù)組中即可,其實(shí)HashMap的擴(kuò)容機(jī)制也是大相徑庭,(ps:HashMap 的初始容量為16,一旦需要擴(kuò)容的時(shí)候會(huì)擴(kuò)大到原來的2倍)關(guān)于HashMap的擴(kuò)容機(jī)制在下一段落中會(huì)詳細(xì)介紹。
源碼解析
構(gòu)造函數(shù)
HashMap在設(shè)計(jì)之處的時(shí)候提供了四個(gè)構(gòu)造函數(shù):

//initialCapcity :初始化容量;
//laodFactor :平衡因子;
在代碼中可以看見,HashMap對(duì)這兩個(gè)值都是有默認(rèn)值的設(shè)計(jì):初始化容量為16,平衡因子為0.75;至于平衡因子為什么會(huì)選擇0.75,JDK中說是平衡了時(shí)間和空間因素的最好取值。
這里解釋下平衡因子這個(gè)東西:平衡因子表示Hash表中元素填滿的程度,前面說到了每一個(gè)key在存入的時(shí)候都會(huì)經(jīng)過hash函數(shù)生成一個(gè)對(duì)應(yīng)的下表,如果說平衡因子越大,也就是hash表的填滿程度越大,這樣出現(xiàn)hash沖突的可能性就會(huì)越大,在做查找的時(shí)候就會(huì)花費(fèi)大量的世間。但是相反的是如果平衡因子越小,這樣空間的利用率就會(huì)降低,出現(xiàn)內(nèi)存浪費(fèi)的情況。所以說,樓主覺得JDK說是平衡了 時(shí)間和空間的因素的最好取值還是有道理的O^O。

Hash函數(shù)設(shè)計(jì)
前面一直說到的hash函數(shù),以及一個(gè)好的hash函數(shù),可以降低hash沖突的發(fā)生,那么HashMap中的hash函數(shù)是怎么設(shè)計(jì)的呢?(這里是JDK1.8的hash函數(shù),每個(gè)版本的JDK中的hash函數(shù)設(shè)計(jì)的是不一樣的)

可以看見代碼的直接意思就是:如果傳入的key 為null那么直接返回0,如果不是null那么就是返回原h(huán)ash值和原h(huán)ash值的無符號(hào)右移16位的異或結(jié)果,(這里相比較JDK之前的版本hash函數(shù)的設(shè)計(jì)變得簡單了很多)。
為什么要這么設(shè)計(jì)呢?
一個(gè)數(shù)右移16位,也就是說任何一個(gè)小于2的16次方的數(shù)向右移16位都會(huì)變成0,在異或運(yùn)中我們知道任何一個(gè)數(shù)在和0做異或運(yùn)算的時(shí)候都會(huì)返回起本身的值(0^1—>1;0^0—>0),那么就很好理解了代碼中在做異或運(yùn)算的右邊只有在大于2的16次方的時(shí)候才會(huì)重新計(jì)算hash值。否則都會(huì)直接返回原來的hash值。
說了半天好像沒說到關(guān)于這樣設(shè)計(jì)的好處在哪里。下面說下這樣設(shè)計(jì)的原理:
首先java中int為4個(gè)字節(jié)也就是2的32次方,這里先看一張圖片:

為了降低hash的沖突在JDK1.8中hash函數(shù)的設(shè)計(jì)中,使用了移位異或運(yùn)算,原來的hash值和右移16位的hash值在做異或運(yùn)算(右位移16位,正好是32bit的一半,自己的高半?yún)^(qū)和低半?yún)^(qū)做異或,就是為了混合原始哈希碼的高位和低位,以此來加大低位的隨機(jī)性),這樣子出現(xiàn)相同的hash值得概率得到了極大的降低。ps:不得不說這種設(shè)計(jì)的方式是真滴【皮】??!
HashMap.Entry
在JDK1.8中HashMap存放對(duì)象的是Node<K,V> 他繼承自Map.Entry<K,V>。

可以看出來Entry<K,V>實(shí)際上實(shí)現(xiàn)了一個(gè)單向鏈表的功能。JDK1.8中使用的是Node<K,V>的靜態(tài)內(nèi)部類,
說到Entry<K,V>這里有必要說下HashMap的遍歷方法。。。。。。。
HashMap的遍歷
HashMap的遍歷方式有很多種,這里主要說兩種方式的遍歷:
①,keySet()這種遍歷方式是最普遍的使用方式,但是并不是最好的選擇,
Map<K,V> map = new hashMap<K,V>();
for(K key : map.keySet()){
? ? ? ?sysout("key = " + key +?。?;"+ "value = " + map.get(key));
}
②,entrySet() 在JDK1.8中這里其實(shí)是Map.Entry<K,V>實(shí)現(xiàn)了AbstrarctMap<Map.Entry<K,V>>
? ? ? ?這種方式也是最快的遍歷方式下面會(huì)解釋這里使用EntrySet遍歷為什么是最快的(相比較)
Map<K,V> map = new HashMap<K,V>();
for(Map.Entry<K,V> node : map.entrySet()){
? ? ? ?sysout("key? = " + node.getKey() +?。alue = " +? node.getValue());
}
③,這里順便說一下在JDK1.8中新添加forEach()用來遍歷集合(forEach()并不推薦使用)
Mapmap = new HashMap();
map.put("1", "aa");
map.put("2", "bb");
map.forEach((k,v) -> System.out.println("k =" + k + "value = " + v));
在數(shù)據(jù)量很大的時(shí)候推薦使用EntrySet遍歷Map
SS:在使用keySet遍歷map的時(shí)候其實(shí)是遍歷了兩次,分別遍歷了鍵和值,相比較而言EntrySet使用一次遍歷直接獲得了Entry(里面包括了key和value),因此推薦使用EntrySet來遍歷
get()操作
public V get(Object key){
? ? //單獨(dú)處理key為null的情況,這里頁證實(shí)了HashMap中是允許鍵為null的情況
? ? if (key ==null){
? ? ? ? ?return getForNullKey ();
? ? }
? ? Entry entry = getEntry(key);
? ? ?return null== entry ?null: entry.getValue();
}
private V getForNullKey(){
? ? if(size ==0) {
? ? ? ? return null;
? ? ?}
? ?//key為null的Entry用于放在table[0]中,但是在table[0]沖突鏈中的Entry的key不一定為? ? ?null
? ?//所以需要遍歷沖突鏈,查找key是否存在
? ? for(Entry e = table[0]; e !=null; e = e.next) {
? ? ? ? if(e.key ==null){
? ? ? ? ? ? returne.value;
? ? ? ? ? }
? ? }
return null;
}
final EntrygetEntry(Object key){
? ? if(size ==0) {
? ? ? ? returnnull;
? ? }
? ? int hash = (key ==null) ?0: hash(key);
? ? //首先定位到索引在table中的位置
? ? //然后遍歷沖突鏈,查找key是否存在
? ? for(Entry 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;
}
第一篇先到這里,