java_HashMap(一)

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>?


Map接口實(shí)現(xiàn)圖

這里實(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。

HashMap的構(gòu)造函數(shù)

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ì)的是不一樣的)


HashMap中的hash函數(shù)

可以看見代碼的直接意思就是:如果傳入的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函數(shù)運(yùn)算原理圖

為了降低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>。

Node<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;

}

第一篇先到這里,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,441評(píng)論 0 16
  • 5.1、對(duì)于HashMap需要掌握以下幾點(diǎn) Map的創(chuàng)建:HashMap() 往Map中添加鍵值對(duì):即put(Ob...
    rochuan閱讀 861評(píng)論 0 0
  • 自從進(jìn)了大學(xué),明明課不少,每天卻還是渾渾噩噩不知所以,跟著周圍的同學(xué)亂混,但真正亂混的卻是自己。我就像站在原地,被...
    成敗與否閱讀 1,316評(píng)論 0 1
  • 這幾天偏愛的零食是瓦片煎餅,一張一張,蛋奶黃,薄而脆。 上課辛苦了,一下課就趕緊取出包里的瓦片煎餅,一包...
    蕙草閱讀 434評(píng)論 0 0
  • 有人跟我說,世界上那么多人,能在一個(gè)國家一個(gè)民族一個(gè)地區(qū)一個(gè)省一個(gè)市一個(gè)學(xué)校一個(gè)班甚至一個(gè)宿舍讓我們相見,東南西北...
    三白當(dāng)浮三大白閱讀 437評(píng)論 0 0

友情鏈接更多精彩內(nèi)容