HashMap源碼分析

1.HashMap的底層實(shí)現(xiàn)圖示


hash表結(jié)構(gòu)圖

如上圖所示:

HashMap底層是由?數(shù)組+(鏈表)=(紅黑樹)組成,每個(gè)存儲(chǔ)在HashMap中的鍵值對(duì)都存放在一個(gè)Node節(jié)點(diǎn)之中豁鲤,其中包含了Key-Value之外秽誊,還包括hash值(key.hashCode()) ^ (h >>> 16)) 以及執(zhí)行下一個(gè)節(jié)點(diǎn)的指針next。

2.源碼分析

2.1幾個(gè)重要常量

static final int DEFAULT_INITIAL_CAPACITY =1 <<4;??HashMap的默認(rèn)容量畅形,16

static final int MAXIMUM_CAPACITY =1 <<30;? HashMap的最大支持容量养距,2^30

static final float DEFAULT_LOAD_FACTOR =0.75f;??HashMap的默認(rèn)加載因子

static final int TREEIFY_THRESHOLD =8;??Bucket中鏈表長(zhǎng)度大于該默認(rèn)值,轉(zhuǎn)化為紅黑樹

static final int UNTREEIFY_THRESHOLD =6;??Bucket中紅黑樹存儲(chǔ)的Node小于該默認(rèn)值日熬,轉(zhuǎn)化為鏈表

static final int MIN_TREEIFY_CAPACITY =64;?桶中的Node被樹化時(shí)最小的hash表容量棍厌。(當(dāng)桶中Node的數(shù)量大到需要變紅黑樹時(shí),若hash表容量小于MIN_TREEIFY_CAPACITY時(shí)竖席,此時(shí)應(yīng)執(zhí)行resize擴(kuò)容操作這個(gè)MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍耘纱。)

transient Node[] table;??存儲(chǔ)元素的數(shù)組,總是2的n次冪

transient Set>entrySet;??存儲(chǔ)具體元素的集

transient int size;?HashMap中存儲(chǔ)的鍵值對(duì)的數(shù)量

transient int modCount;?HashMap擴(kuò)容和結(jié)構(gòu)改變的次數(shù)毕荐。

int threshold;?擴(kuò)容的臨界值束析,=容量*填充因子

final float loadFactor;??填充因子

2.2幾個(gè)重要方法

1)無參構(gòu)造方法

public HashMap() {

????this.loadFactor =DEFAULT_LOAD_FACTOR;// all other fields defaulted

}

無參構(gòu)造方法,設(shè)定了負(fù)載因子為0.75的默認(rèn)值

2)指定容量的構(gòu)造方法

public HashMap(int initialCapacity) {

????this(initialCapacity,DEFAULT_LOAD_FACTOR);

}

這種構(gòu)造方法憎亚,最后會(huì)調(diào)用3中的指定容量和負(fù)載因子的構(gòu)造方法员寇,將負(fù)載因子設(shè)置為默認(rèn)值

3)指定容量和負(fù)載因子的構(gòu)造方法

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

}

這個(gè)構(gòu)造方法來確定負(fù)載因子擴(kuò)容臨界值,最后調(diào)用tableSizeFor方法來計(jì)算擴(kuò)容臨界值

4)根據(jù)鍵值對(duì)數(shù)量獲取HashMap容量方法? ?tableSizeFor

static final int tableSizeFor(int cap) {

????int n = cap -1;

????n |= n >>>1;

????n |= n >>>2;

????n |= n >>>4;

????n |= n >>>8;

????n |= n >>>16;

????return (n <0) ?1 : (n >=MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : n +1;

}

tabSizeFor方法第美,主要根據(jù)傳入的鍵值對(duì)容量蝶锋,來返回大于容量的最小的二次冪數(shù)值。

算法如下:

將傳入的容量-1:至于這里為什么需要減1什往,是為了防止cap已經(jīng)是2的冪扳缕。如果cap已經(jīng)是2的冪, 又沒有執(zhí)行這個(gè)減1操作,則執(zhí)行完后面的幾條無符號(hào)右移操作之后,返回的capacity將是這個(gè)cap的2倍。

假設(shè)原始n:? ? 0001? xxxx xxxx xxxx

第一次右移1位+或運(yùn)算:二進(jìn)制序列出現(xiàn)至少兩個(gè)連續(xù)的1,如 0001 1xxx xxxx xxxx躯舔;

第二次右移2位+或運(yùn)算:二進(jìn)制序列出現(xiàn)至少四個(gè)連續(xù)的1驴剔,如 0001 111x xxxx xxxx;

第三次右移4位+或運(yùn)算:二進(jìn)制序列出現(xiàn)至少八個(gè)連續(xù)的1, 如 0001 1111 1111 xxxx;

第四次右移8位+或運(yùn)算:二進(jìn)制序列至少出現(xiàn)16個(gè)連續(xù)的1粥庄,如 0001 1111 1111 1111;

第五次右移16位+或運(yùn)算:二進(jìn)制序列至少出現(xiàn)32個(gè)連續(xù)的1丧失,如 0001 1111 1111 1111;

上述運(yùn)算中,若出現(xiàn)右移后為0惜互,則或運(yùn)算得到的結(jié)果和原始值一致利花,則后續(xù)推導(dǎo)過程可以忽略。

此時(shí)可以保證载佳,原始序列從包含1的最高位炒事,到最低位,全部都變成了1.

最后+1蔫慧,返回的結(jié)果就是大于原值的最小二次冪數(shù)挠乳。

5)hash方法

static final int hash(Object key) {

?????int h;

? ? ?return (key ==null) ?0 : (h = key.hashCode()) ^ (h >>>16);

}

hash方法用傳入的key的hashCode和hashCode無符號(hào)右移16位的結(jié)果,做異或運(yùn)算后作為hash值返回姑躲。

注:之所以獲取hashCode后睡扬,還需要和右移16位的hashCode做異或運(yùn)算,原因是:在根據(jù)hash值獲取鍵值對(duì)在bucket數(shù)組中的下標(biāo)時(shí)黍析,采用的算法是:index=h & (length-1),當(dāng)數(shù)組的length較小時(shí)卖怜,只有低位能夠參與到“與”運(yùn)算中,但是將hashCode右移16位再與本身做異或獲取到的hash阐枣,可以使高低位均能夠參與到后面的與運(yùn)算中马靠。

下面圖說明:

6)插入方法? ?putVal

final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict) {

????Node<K,V>[] tab; Node<K,V> p;int n, i;??//存儲(chǔ)Node節(jié)點(diǎn)的數(shù)組tab,單個(gè)Node節(jié)點(diǎn)p,HashMap的容量n

????if ((tab =table) ==null || (n = tab.length) ==0)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//初始化數(shù)組桶table (首次進(jìn)入初始化)

????????n = (tab = resize()).length;

????if ((p = tab[i = (n -1) & hash]) ==null)? ?????//如果數(shù)組桶中不包含要插入的元素,將新鍵值對(duì)作為新Node存入數(shù)組(當(dāng)前tab中無沖突元素)

????????tab[i] = newNode(hash, key, value,null);

????else {? ?????????????????????????????????????????????????????????????????????????????????????????????????????//桶中包含要插入的元素(tab中已有元素蔼两,發(fā)生沖突)

????????Node<K,V> e;K k;? ? ? ? ? //e用來保存當(dāng)前key已存在時(shí)的節(jié)點(diǎn)

????????if (p.hash == hash &&((k = p.key) == key || (key !=null && key.equals(k))))? ??//如果key和鏈表第一個(gè)元素p的key相等

????????????e = p;

????????else if (pinstanceof TreeNode)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???//若p是TreeNode類型甩鳄,則使用紅黑樹的方法插入到樹中

????????????e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

????????else {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//鍵值對(duì)的引用不在鏈表的第一個(gè)節(jié)點(diǎn),此時(shí)需要遍歷鏈表

????????????for (int binCount =0; ; ++binCount) {

????????????????if ((e = p.next) ==null) {? ? ? ? ? ? ? //將p.next指向e额划,并判斷p是否為最后一個(gè)節(jié)點(diǎn)妙啃,若是插入新節(jié)點(diǎn),此時(shí)e==null

????????????????????p.next = newNode(hash, key, value,null);

????????????????????if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st? ?俊戳,若鏈表長(zhǎng)度大于等于8揖赴,變紅黑樹,退出遍歷

? ? ? ? ? ? ? ? ? ? ? ? treeifyBin(tab, hash);

????????????????????break;

????????????????}

????????????????if (e.hash == hash &&((k = e.key) == key || (key !=null && key.equals(k))))? ?//找到與當(dāng)前key值相同的節(jié)點(diǎn)

????????????????????break;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //退出遍歷(此時(shí)抑胎,e指向與當(dāng)前key值相同的舊節(jié)點(diǎn))

????????????????p = e;? ?????????????????????//將e指向p,便于下次遍歷e = p.next

????????????}

????????}

????????if (e !=null) {// existing mapping for key? ? ??當(dāng)e非空時(shí)燥滑,說明e是原來HashMap中的元素,具有和新節(jié)點(diǎn)一樣的key值

? ? ? ? ? ? V oldValue = e.value;

????????????if (!onlyIfAbsent || oldValue ==null)? ? ?????//onlyIfAbsent 表示是否僅在 oldValue 為 null 的情況下更新鍵值對(duì)的值

????????????????e.value = value;

????????????afterNodeAccess(e);? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//空實(shí)現(xiàn)圆恤,LinkedHashMap用

????????????return oldValue;

????????}

????}

????++modCount;????????????????????????????????????????//HashMap結(jié)構(gòu)更改突倍,modCount+1

????if (++size >threshold)????????????????????????????//判斷是否需要擴(kuò)容

????????resize();

????afterNodeInsertion(evict);? ? ? ? ? ? ? ? ? ? ?//空實(shí)現(xiàn),LinkedHashMap用

????return null;

}

HashMap中進(jìn)行存儲(chǔ)的入口方法是:put(K盆昙,V)羽历,但是核心方法是putVal方法,該方法一共有以下步驟:

1.初始化數(shù)組桶

2.判斷數(shù)組桶中對(duì)應(yīng)下標(biāo)是否無元素存在淡喜,是秕磷,就直接存入

3.若數(shù)組桶中對(duì)應(yīng)下標(biāo)有元素存在,則開始遍歷炼团,根據(jù)長(zhǎng)度將元素存入鏈表尾部或樹中澎嚣。

4.判斷是否需要擴(kuò)容

7)擴(kuò)容方法? ?resize

? final Node[] resize() {

????Node[] oldTab =table;

????int oldCap = (oldTab ==null) ?0 : oldTab.length;? ? ? ? ? ? ?//原HashMap的容量

????int oldThr =threshold;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//原HashMap的擴(kuò)容臨界值

????int newCap, newThr =0;

????if (oldCap >0) {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//case1 : odlCap>0,說明桶數(shù)組已經(jīng)初始化過

????????if (oldCap >=MAXIMUM_CAPACITY) {??//原HashMap的越界檢查

????????????threshold = Integer.MAX_VALUE;

????????????return oldTab;

????????}

????????else if ((newCap = oldCap <<1) <?MAXIMUM_CAPACITY &&? oldCap >=DEFAULT_INITIAL_CAPACITY)? ??//容量擴(kuò)大一倍后的越界檢查

????????????newThr = oldThr <<1;// double threshold

? ? }

????????//case2:oldCap=0 && oldThr >0,桶數(shù)組尚未初始化,當(dāng)調(diào)用帶初始化容量的構(gòu)造函數(shù)時(shí)會(huì)發(fā)生該情況

????else if (oldThr >0)// initial capacity was placed in threshold??

? ? ? ? newCap = oldThr;??????//在前面HashMap的初始化中瘟芝,將Initial capcity暫存在threshold中,新的閥值按照下面newThr==0中的公式進(jìn)行計(jì)算

????????//case3:oldCap=0 && oldThr = 0易桃,當(dāng)調(diào)用無參構(gòu)造函數(shù)時(shí)會(huì)發(fā)生該情況,此時(shí)使用默認(rèn)容量初始化

????else {// zero initial threshold signifies using defaults

? ? ? ? newCap =DEFAULT_INITIAL_CAPACITY;??//默認(rèn)容量

????????newThr = (int)(DEFAULT_LOAD_FACTOR *DEFAULT_INITIAL_CAPACITY);??//默認(rèn)擴(kuò)容臨界值

????}

????if (newThr ==0) {? ? //case2中锌俱,調(diào)用Initial capcity構(gòu)造方法時(shí)晤郑,該閥值為0,需計(jì)算閥值

????????float ft = (float)newCap *loadFactor;

????????newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 35(int)ft : Integer.MAX_VALUE);

????}

????threshold = newThr;

????@SuppressWarnings({"rawtypes","unchecked"})

????????Node[] newTab = (Node[])new Node[newCap];??//上面獲取到的新的Capcity贸宏,來創(chuàng)建一個(gè)新的桶數(shù)組 newTab造寝,并指向table

????table = newTab;

????if (oldTab !=null) {? ??//若oldTab非空,則需要將原來桶數(shù)組的元素取出來放到新的桶數(shù)組中

????????for (int j =0; j < oldCap; ++j) {

????????????Node e;

????????????if ((e = oldTab[j]) !=null) {

????????????????oldTab[j] =null;? ??//將原桶數(shù)組的元素占用的空間釋放吭练,便于GC

????????????????if (e.next ==null)? ?//若桶中元素的next為空诫龙,獲取index后直接將其放入新桶數(shù)組中

????????????????????newTab[e.hash & (newCap -1)] = e;

? ? ? ? ? ? ? ? else if (einstanceof TreeNode)? ?//若桶中元素的next是樹節(jié)點(diǎn)

????????????????????((TreeNode)e).split(this, newTab, j, oldCap);? ??//采用樹的方式插入

????????????????else {// preserve order? ? ? ? ??若桶中元素的next是鏈表節(jié)點(diǎn)

? ? ? ? ? ? ? ? ? ? Node loHead =null, loTail =null;

????????????????????Node hiHead =null, hiTail =null;

????????????????????Node next;

? ? ? ? ? ? ? ? ? ? do {

? ? ? ? ? ? ? ? ? ? ? ? ? next = e.next;

? ? ? ? ? ? ? ? ? ? ? ? ? if ((e.hash & oldCap) ==0) {? ??//若e.e.hash & oldCap 結(jié)果為0,則下標(biāo)在新桶數(shù)組中不用改變,此時(shí),將元素存放在loHead為首的鏈表中

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?if (loTail ==null)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loHead = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?else

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loTail.next = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? loTail = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ? ?else {? ? ? ? ? ? ? ? ? ? ? ? //若e.e.hash & oldCap 結(jié)果不為0赖瞒,則下標(biāo)在新桶數(shù)組等于原下標(biāo)+oldCap崖媚,此時(shí),將元素存放在hiHead為首的鏈表中

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?if (hiTail ==null)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hiHead = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?else

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hiTail.next = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? hiTail = e;

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ?}while ((e = next) !=null);

? ? ? ? ? ? ? ? ? ? ? ? ?if (loTail !=null) {? ? ? //將小于原來容量的頭節(jié)點(diǎn)放入原來數(shù)組中位置

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?loTail.next =null;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?newTab[j] = loHead;

? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? ?if (hiTail !=null) {? ? ??//將大于原來容量的頭節(jié)點(diǎn)放入(原來數(shù)組+擴(kuò)容大星诳)中位置

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?hiTail.next =null;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?newTab[j + oldCap] = hiHead;

? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ?return newTab;

}

/*原始鏈表中的元素,在resize之后,其下標(biāo)有兩種可能括丁,一種是在原來下標(biāo)處,另一種是原來下標(biāo)+oldCap處?

?*舉例說明: 若原來的容量 -1后 只有n位伶选,低位有n個(gè)1史飞,去下標(biāo)公式為:i = (oldCap - 1) & hash,若hash值只有低n為有值仰税,則與運(yùn)算后獲得的值和?

?*擴(kuò)容前是一樣的构资,若hash不止第n位有值,那采用與運(yùn)算后陨簇,結(jié)果比原來剛好大oldCap吐绵。 下面有圖片示例)?

*/

  上述代碼分析較長(zhǎng),總結(jié)如下:

1.獲取不同情況下的 新的容量 和 新的擴(kuò)容臨界值

2.根據(jù)新容量創(chuàng)建新的桶數(shù)組tab。

3.根據(jù)節(jié)點(diǎn)類型己单,樹節(jié)點(diǎn)和鏈表節(jié)點(diǎn)分別采用對(duì)應(yīng)方法放入新的桶數(shù)組

8)查找元素方法 getNode

final Node getNode(int hash, Object key) {

????Node[] tab; Node first, e;int n;K k;

????if ((tab =table) !=null && (n = tab.length) >0 && (first = tab[(n -1) & hash]) !=null) {? ??//根據(jù)hash值唉窃,獲取對(duì)應(yīng)下標(biāo)的第一個(gè)元素first

????????if (first.hash == hash && ((k = first.key) == key || (key !=null && key.equals(k))))// always check first node? 如果first的key和待查詢的key相等,返回first

????????????return first;

????????if ((e = first.next) !=null) {? ?//若first不是待查詢的元素

????????????if (firstinstanceof TreeNode)? ??//若first是樹節(jié)點(diǎn)纹笼,采用樹節(jié)點(diǎn)的方式獲取

????????????????return ((TreeNode)first).getTreeNode(hash, key);

????????????do {

????????????????if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k))))? ?//first是鏈表節(jié)點(diǎn)頭纹份,使用循環(huán)獲取

????????????????????return e;

????????????}while ((e = e.next) !=null);

????????}

????}

????return null;

}

查詢?cè)氐娜肟诜椒ㄊ牵簆ublic V get(Object key),返回值是node的value廷痘,核心方法是getNode(int hash, Object key)蔓涧。

9)刪除元素 removeNode方法

final Node removeNode(int hash, Object key, Object value,?boolean matchValue,boolean movable) {

????Node[] tab; Node p;int n, index;

????//通過hash值獲取下標(biāo),下標(biāo)對(duì)應(yīng)的節(jié)點(diǎn)p不為空

????if ((tab =table) !=null && (n = tab.length) >0 && (p = tab[index = (n -1) & hash]) !=null) {?

????????Node node =null, e;K k;V v;

????????if (p.hash == hash && ((k = p.key) == key || (key !=null && key.equals(k))))? ??//若節(jié)點(diǎn)p的key和待移除的節(jié)點(diǎn)key相等

????????????node = p;? ?//將p指向待移除節(jié)點(diǎn)

????????else if ((e = p.next) !=null) {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//p的key和待移除的節(jié)點(diǎn)key不相等笋额,遍歷p作為頭的鏈表或者樹

????????????if (pinstanceof TreeNode)? ? ? ? ? ? ? ?//采用樹節(jié)點(diǎn)方式獲得要移除的節(jié)點(diǎn)

????????????????node = ((TreeNode)p).getTreeNode(hash, key);

????????????else {? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//p是鏈表的頭節(jié)點(diǎn)

????????????????do {

? ? ? ? ? ? ? ? ? ? //采用循環(huán)元暴,當(dāng)p.next不為空,比對(duì)它和傳入的key兄猩,直到找到相等的key

????????????????????if (e.hash == hash && ((k = e.key) == key || (key !=null && key.equals(k)))) {

????????????????????????node = e;? ?//找到后昨寞,將節(jié)點(diǎn)指向node

????????????????????????break;? ? ? ?//將e指向待移除節(jié)點(diǎn),此時(shí)相當(dāng)于p.next就是待移除的節(jié)點(diǎn)node

? ? ? ? ? ? ? ? ????}

????????????????????p = e;

????????????????}while ((e = e.next) !=null);

????????????}

????????}

????????//若node非空厦滤,傳入的matchValue參數(shù)為flase或 node的value等于傳入value

????????if (node !=null && (!matchValue || (v = node.value) == value || (value !=null && value.equals(v)))) {

????????????if (nodeinstanceof TreeNode)? ? ??//若node是樹節(jié)點(diǎn)

????????????????((TreeNode)node).removeTreeNode(this, tab, movable);

????????????else if (node == p)? ? ??//若待移除節(jié)點(diǎn)是鏈表頭援岩,將其指向待移除元素的next,移除對(duì)node的引用

????????????????tab[index] = node.next;

????????????else? ? ? ? ? ? ? ? ? ? ? ? ? ?//待移除元素是鏈表中的元素掏导,此時(shí)其等于p.next

? ? ? ? ? ? ? ? p.next = node.next;? ? ? ? ? ? ?//將p.next指向node.next享怀,移除了對(duì)node的引用

????????????++modCount;?

????????????--size;

????????????afterNodeRemoval(node);

????????????return node;

????????}

????}

????return null;

}

移除節(jié)點(diǎn)的入口方法是:?public V remove(Object key)? ,其核心方法是removeNode趟咆,主要做了以下幾個(gè)工作:

1.通過用key獲取的hash添瓷,來獲取下標(biāo)。

2.若下標(biāo)對(duì)應(yīng)處無元素值纱,返回null鳞贷。

3.若下標(biāo)對(duì)應(yīng)處有元素,判斷是樹或者鏈表虐唠,采用對(duì)應(yīng)方法移除搀愧。

3.常見問題

3.1HashMap的容量為什么必須是2的n次冪?

1.由上述實(shí)例可以看出疆偿,當(dāng)HashMap容量為2的n次冪的時(shí)候咱筛,length-1,可以使得在計(jì)算index的"&"運(yùn)算過程中杆故,hash值的對(duì)應(yīng)位都能參與到計(jì)算迅箩;若HashMap容量不等于2的n次冪,leng-1后必然有一些位是等于0的处铛,那么在計(jì)算index的"&"運(yùn)算過程中饲趋,對(duì)應(yīng)位的數(shù)字無論是0或者1拐揭,都未能參與到運(yùn)算中。導(dǎo)致Hash沖突概率增大奕塑。

2.初次之外堂污,若HashMap容量不為2的n次冪,無論Hash值如何變化爵川,始終有一些下標(biāo)值無法取得,因?yàn)?&"運(yùn)算過程中息楔,必然有一些位置結(jié)果始終為0寝贡,如實(shí)例所示,其最低位始終為0值依,因此下標(biāo) 1(二進(jìn)制0000 0001)圃泡、3(二進(jìn)制0000 0011)、5(二進(jìn)制0000 0101)愿险、7(二進(jìn)制0000 0111)等下標(biāo)颇蜡、永遠(yuǎn)都獲取不了。造成了容量的浪費(fèi)

綜上:? 當(dāng)容量是2的n次冪時(shí)辆亏,不同的key獲取在桶數(shù)組中的下標(biāo)相同的概率減小风秤,發(fā)生Hash碰撞幾率減少,元素分布更加均勻扮叨,見下圖缤弦。

3.2 HashMap的時(shí)間復(fù)雜度?

O(1)或者O(log(n))或O(n),分析如下:

根據(jù)第一節(jié)的內(nèi)容可知彻磁,根據(jù)HashMap的數(shù)據(jù)結(jié)構(gòu)碍沐,可能有以下三種情況:

1.無鏈表和紅黑樹,理想狀態(tài)衷蜓。每個(gè)桶只有一個(gè)或0個(gè)元素累提,不存在Hash沖突,此時(shí)時(shí)間復(fù)雜度為O(1);但此時(shí)耗費(fèi)的內(nèi)存空間較大磁浇。

2.只有鏈表斋陪,此時(shí)因?yàn)樾枰h(huán)鏈表來獲取元素,時(shí)間復(fù)雜度為O(n)

3.只有紅黑樹置吓,此時(shí)時(shí)間復(fù)雜度為紅黑樹查找的時(shí)間復(fù)雜度鳍贾,O(log(n)).

4.鏈表和紅黑樹均有,此時(shí)時(shí)間復(fù)雜度依據(jù)紅黑樹的層數(shù)和鏈表長(zhǎng)度而定交洗。為O(n)或者O(log(n)).

3.3 負(fù)載因子LoadFactor為何默認(rèn)值為0.75骑科。

當(dāng)負(fù)載因子過大時(shí),Hash沖突概率增加构拳、讀寫的時(shí)間復(fù)雜度也大大增加咆爽,當(dāng)負(fù)載因子過小時(shí)梁棠,Hash沖突概率較小,時(shí)間復(fù)雜度較低斗埂,但占用內(nèi)存空間較大符糊。至于為什么默認(rèn)值是0.75,這是一個(gè)基于時(shí)間和空間復(fù)雜度的綜合考慮的結(jié)果呛凶,可以參考這篇文章:HashMap的loadFactor為什么是0.75男娄?

3.4 作為HasHMap的key有什么條件?

使用HashMap漾稀,若用int/String等作為key值模闲,因?yàn)镮nteger類和String類都以及重寫了equals()和hashCode()方法.但是如果key是自定義的類,就必須重寫hashcode()和equals()崭捍。理由如下:

//在插入元素中尸折,根據(jù)hash值后,與length-1做&運(yùn)算獲取下標(biāo)

? ? ? //獲取hash

????static final int hash(Object key) {

? ? ? ? int h;

? ? ? ? return(key ==null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

? ? }

? ? //獲取下標(biāo)p = tab[i = (n - 1) & hash]

? ? //用equals方法比對(duì)key值和節(jié)點(diǎn)的key值殷蛇,來確認(rèn)是否遍歷到所需元素(key !=null&& key.equals(k)))

????//對(duì)比hash值实夹,如果不重寫hashCode方法,那么采用Object類的默認(rèn)的hash方法是獲取內(nèi)存地址粒梦,此時(shí)即使兩個(gè)key對(duì)象相等亮航,但其內(nèi)存地址不可能相等,所以必須重寫hashCode方法

????//equals方法若不重寫匀们,采用的Object的equals方法塞赂,比對(duì)的是內(nèi)存地址,如果不重寫昼蛀,會(huì)造成兩個(gè)一樣的key值都插入宴猾,存在重復(fù)元素*/

????//同理,在查找過程中叼旋,在第二節(jié)putVal方法中有分析仇哆,會(huì)用到hash值,以及用到key.equals方法夫植,因此如果不重寫equals()和hashCode()讹剔,會(huì)造成雖然元素存在,但是因內(nèi)存地址不一致详民,差找不到對(duì)應(yīng)元素延欠。

3.5HashMap key允許為空嗎?沈跨,最多幾個(gè)由捎?

允許但只允許一個(gè)key值為空。當(dāng)key值為空時(shí)饿凛,其hash值為0狞玛,因此在桶數(shù)組中位置是0软驰,即第一個(gè)元素。

那么為什么不能有兩個(gè)key值為null呢心肪,原因是兩個(gè)key為null锭亏,是一樣的,后面put進(jìn)去的(null,value2)會(huì)覆蓋先put進(jìn)去的(null,value1)


摘自:https://www.cnblogs.com/LearnAndGet/p/9971526.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硬鞍,一起剝皮案震驚了整個(gè)濱河市慧瘤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌固该,老刑警劉巖锅减,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蹬音,居然都是意外死亡上煤,警方通過查閱死者的電腦和手機(jī)休玩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門著淆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拴疤,你說我怎么就攤上這事永部。” “怎么了呐矾?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵苔埋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蜒犯,道長(zhǎng)组橄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任罚随,我火速辦了婚禮玉工,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淘菩。我一直安慰自己遵班,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布潮改。 她就那樣靜靜地躺著狭郑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汇在。 梳的紋絲不亂的頭發(fā)上翰萨,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音糕殉,去河邊找鬼缨历。 笑死以蕴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辛孵。 我是一名探鬼主播丛肮,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼魄缚!你這毒婦竟也來了宝与?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤冶匹,失蹤者是張志新(化名)和其女友劉穎习劫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚼隘,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诽里,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飞蛹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤狡。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卧檐,靈堂內(nèi)的尸體忽然破棺而出墓懂,到底是詐尸還是另有隱情,我是刑警寧澤霉囚,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布捕仔,位于F島的核電站,受9級(jí)特大地震影響盈罐,放射性物質(zhì)發(fā)生泄漏榜跌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一盅粪、第九天 我趴在偏房一處隱蔽的房頂上張望钓葫。 院中可真熱鬧,春花似錦湾揽、人聲如沸瓤逼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霸旗。三九已至,卻和暖如春戚揭,著一層夾襖步出監(jiān)牢的瞬間诱告,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工民晒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留精居,地道東北人锄禽。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像靴姿,于是被迫代替她去往敵國(guó)和親沃但。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1.HashMap是一個(gè)數(shù)組+鏈表/紅黑樹的結(jié)構(gòu)佛吓,數(shù)組的下標(biāo)在HashMap中稱為Bucket值宵晚,每個(gè)數(shù)組項(xiàng)對(duì)應(yīng)的...
    誰在烽煙彼岸閱讀 1,024評(píng)論 2 2
  • Java是一種可以撰寫跨平臺(tái)應(yīng)用軟件的面向?qū)ο蟮某绦蛟O(shè)計(jì)語言。Java 技術(shù)具有卓越的通用性维雇、高效性淤刃、平臺(tái)移植性和...
    Java小辰閱讀 232評(píng)論 0 0
  • 一、HashMap概述 HashMap基于哈希表的Map接口的實(shí)現(xiàn)吱型。此實(shí)現(xiàn)提供所有可選的映射操作逸贾,并允許使用nul...
    小陳阿飛閱讀 637評(píng)論 0 2
  • 一铝侵、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,261評(píng)論 0 16
  • 我發(fā)現(xiàn)自我激勵(lì)是個(gè)技術(shù)活,如果不持續(xù)給自己動(dòng)力据沈,就很容易松懈哟沫。 不知道從什么時(shí)候開始饺蔑,我一直對(duì)自我積極地暗示以及自...
    Rechel218閱讀 245評(píng)論 2 2