esp8266接收到的数据如何存放到数组中_Java中HashMap的实现原理
最近面試中被問(wèn)及Java中HashMap的原理,瞬間無(wú)言以對(duì),因此痛定思痛覺(jué)得研究一番。
一、Java中的hashCode和equals
1、關(guān)于hashCode
再歸納一下就是hashCode是用于查找使用的,而equals是用于比較兩個(gè)對(duì)象的是否相等的。
以下對(duì)hashCode的解讀摘自其他博客:
1.hashcode是用來(lái)查找的,如果你學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)就應(yīng)該知道,在查找和排序這一章有例如內(nèi)存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個(gè)類(lèi),這個(gè)類(lèi)有個(gè)字段叫ID,我要把這個(gè)類(lèi)存放在以上8個(gè)位置之一,如果不用hashcode而任意存放,那么當(dāng)查找時(shí)就需要到這八個(gè)位置里挨個(gè)去找,或者用二分法一類(lèi)的算法。
但如果用hashcode那就會(huì)使效率提高很多。
我們這個(gè)類(lèi)中有個(gè)字段叫ID,那么我們就定義我們的hashcode為ID%8,然后把我們的類(lèi)存放在取得得余數(shù)那個(gè)位置。比如我們的ID為9,9除8的余數(shù)為1,那么我們就把該類(lèi)存在1這個(gè)位置,如果ID是13,求得的余數(shù)是5,那么我們就把該類(lèi)放在5這個(gè)位置。這樣,以后在查找該類(lèi)時(shí)就可以通過(guò)ID除 8求余數(shù)直接找到存放的位置了。
2.但是如果兩個(gè)類(lèi)有相同的hashcode怎么辦那(我們假設(shè)上面的類(lèi)的ID不是唯一的),例如9除以8和17除以8的余數(shù)都是1,那么這是不是合法的,回答是:可以這樣。那么如何判斷呢?在這個(gè)時(shí)候就需要定義 equals了。
也就是說(shuō),我們先通過(guò) hashcode來(lái)判斷兩個(gè)類(lèi)是否存放某個(gè)桶里,但這個(gè)桶里可能有很多類(lèi),那么我們就需要再通過(guò) equals 來(lái)在這個(gè)桶里找到我們要的類(lèi)。
那么。重寫(xiě)了equals(),為什么還要重寫(xiě)hashCode()呢?
想想,你要在一個(gè)桶里找東西,你必須先要找到這個(gè)桶啊,你不通過(guò)重寫(xiě)hashcode()來(lái)找到桶,光重寫(xiě)equals()有什么用啊
2、關(guān)于equals
1.equals和==
==用于比較引用和比較基本數(shù)據(jù)類(lèi)型時(shí)具有不同的功能:
比較基本數(shù)據(jù)類(lèi)型,如果兩個(gè)值相同,則結(jié)果為true
而在比較引用時(shí),如果引用指向內(nèi)存中的同一對(duì)象,結(jié)果為true;
equals()作為方法,實(shí)現(xiàn)對(duì)象的比較。由于==運(yùn)算符不允許我們進(jìn)行覆蓋,也就是說(shuō)它限制了我們的表達(dá)。因此我們復(fù)寫(xiě)equals()方法,達(dá)到比較對(duì)象內(nèi)容是否相同的目的。而這些通過(guò)==運(yùn)算符是做不到的。
2.object類(lèi)的equals()方法的比較規(guī)則為:如果兩個(gè)對(duì)象的類(lèi)型一致,并且內(nèi)容一致,則返回true,這些類(lèi)有:
java.io.file,java.util.Date,java.lang.string,包裝類(lèi)(Integer,Double等)
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
運(yùn)行結(jié)果為false true
二、HashMap的實(shí)現(xiàn)原理
1. HashMap概述
HashMap是基于哈希表的Map接口的非同步實(shí)現(xiàn)。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類(lèi)不保證映射的順序,特別是它不保證該順序恒久不變。
在java編程語(yǔ)言中,最基本的結(jié)構(gòu)就是兩種,一個(gè)是數(shù)組,另外一個(gè)是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個(gè)基本結(jié)構(gòu)來(lái)構(gòu)造的,HashMap也不例外。HashMap實(shí)際上是一個(gè)“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。
從上圖中可以看出,HashMap底層就是一個(gè)數(shù)組結(jié)構(gòu),數(shù)組中的每一項(xiàng)又是一個(gè)鏈表。當(dāng)新建一個(gè)HashMap的時(shí)候,就會(huì)初始化一個(gè)數(shù)組。
其中Java源碼如下:
/** * The table, resized as necessary. Length MUST Always be a power of two. */ 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就是數(shù)組中的元素,每個(gè) Map.Entry 其實(shí)就是一個(gè)key-value對(duì),它持有一個(gè)指向下一個(gè)元素的引用,這就構(gòu)成了鏈表。
2、HashMap實(shí)現(xiàn)存儲(chǔ)和讀取
1)存儲(chǔ)
1 public V put(K key, V value) { 2 // HashMap允許存放null鍵和null值。 3 // 當(dāng)key為null時(shí),調(diào)用putForNullKey方法,將value放置在數(shù)組第一個(gè)位置。 4 if (key == null) 5 return putForNullKey(value); 6 // 根據(jù)key的keyCode重新計(jì)算hash值。 7 int hash = hash(key.hashCode()); 8 // 搜索指定hash值在對(duì)應(yīng)table中的索引。 9 int i = indexFor(hash, table.length); 10 // 如果 i 索引處的 Entry 不為 null,通過(guò)循環(huán)不斷遍歷 e 元素的下一個(gè)元素。 11 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 12 Object k; 13 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 14 // 如果發(fā)現(xiàn)已有該鍵值,則存儲(chǔ)新的值,并返回原始值 15 V oldValue = e.value; 16 e.value = value; 17 e.recordAccess(this); 18 return oldValue; 19 } 20 } 21 // 如果i索引處的Entry為null,表明此處還沒(méi)有Entry。 22 modCount++; 23 // 將key、value添加到i索引處。 24 addEntry(hash, key, value, i); 25 return null; 26 }
根據(jù)hash值得到這個(gè)元素在數(shù)組中的位置(即下標(biāo)),如果數(shù)組該位置上已經(jīng)存放有其他元素了,那么在這個(gè)位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。如果數(shù)組該位置上沒(méi)有元素,就直接將該元素放到此數(shù)組中的該位置上。
hash(int h)方法根據(jù)key的hashCode重新計(jì)算一次散列。此算法加入了高位計(jì)算,防止低位不變,高位變化時(shí),造成的hash沖突。
1 static int hash(int h) { 2 h ^= (h >>> 20) ^ (h >>> 12); 3 return h ^ (h >>> 7) ^ (h >>> 4); 4 }
我們可以看到在HashMap中要找到某個(gè)元素,需要根據(jù)key的hash值來(lái)求得對(duì)應(yīng)數(shù)組中的位置。如何計(jì)算這個(gè)位置就是hash算法。前面說(shuō)過(guò)HashMap的數(shù)據(jù)結(jié)構(gòu)是數(shù)組和鏈表的結(jié)合,所以我們當(dāng)然希望這個(gè)HashMap里面的元素位置盡量的分布均勻些,盡量使得每個(gè)位置上的元素?cái)?shù)量只有一個(gè),那么當(dāng)我們用hash算法求得這個(gè)位置的時(shí)候,馬上就可以知道對(duì)應(yīng)位置的元素就是我們要的,而不用再去遍歷鏈表,這樣就大大優(yōu)化了查詢的效率。
根據(jù)上面 put 方法的源代碼可以看出,當(dāng)程序試圖將一個(gè)key-value對(duì)放入HashMap中時(shí),程序首先根據(jù)該 key的 hashCode() 返回值決定該 Entry 的存儲(chǔ)位置:如果兩個(gè) Entry 的 key 的 hashCode() 返回值相同,那它們的存儲(chǔ)位置相同。如果這兩個(gè) Entry 的 key 通過(guò) equals 比較返回 true,新添加 Entry 的 value 將覆蓋集合中原有 Entry的 value,但key不會(huì)覆蓋。如果這兩個(gè) Entry 的 key 通過(guò) equals 比較返回 false,新添加的 Entry 將與集合中原有 Entry 形成 Entry 鏈,而且新添加的 Entry 位于 Entry 鏈的頭部——具體說(shuō)明繼續(xù)看 addEntry() 方法的說(shuō)明。
通過(guò)這種方式就可以高效的解決HashMap的沖突問(wèn)題。
2)讀取
1 public V get(Object key) { 2 if (key == null) 3 return getForNullKey(); 4 int hash = hash(key.hashCode()); 5 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 6 e != null; 7 e = e.next) { 8 Object k; 9 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 10 return e.value; 11 } 12 return null; 13 }
從HashMap中g(shù)et元素時(shí),首先計(jì)算key的hashCode,找到數(shù)組中對(duì)應(yīng)位置的某一元素,然后通過(guò)key的equals方法在對(duì)應(yīng)位置的鏈表中找到需要的元素。
3)歸納起來(lái)簡(jiǎn)單地說(shuō),HashMap 在底層將 key-value 當(dāng)成一個(gè)整體進(jìn)行處理,這個(gè)整體就是一個(gè) Entry 對(duì)象。HashMap 底層采用一個(gè) Entry[] 數(shù)組來(lái)保存所有的 key-value 對(duì),當(dāng)需要存儲(chǔ)一個(gè) Entry 對(duì)象時(shí),會(huì)根據(jù)hash算法來(lái)決定其在數(shù)組中的存儲(chǔ)位置,在根據(jù)equals方法決定其在該數(shù)組位置上的鏈表中的存儲(chǔ)位置;當(dāng)需要取出一個(gè)Entry時(shí),也會(huì)根據(jù)hash算法找到其在數(shù)組中的存儲(chǔ)位置,再根據(jù)equals方法從該位置上的鏈表中取出該Entry。
3、HashMap的resize
當(dāng)hashmap中的元素越來(lái)越多的時(shí)候,碰撞的幾率也就越來(lái)越高(因?yàn)閿?shù)組的長(zhǎng)度是固定的),所以為了提高查詢的效率,就要對(duì)hashmap的數(shù)組進(jìn)行擴(kuò)容,數(shù)組擴(kuò)容這個(gè)操作也會(huì)出現(xiàn)在ArrayList中,所以這是一個(gè)通用的操作,很多人對(duì)它的性能表示過(guò)懷疑,不過(guò)想想我們的“均攤”原理,就釋然了,而在hashmap數(shù)組擴(kuò)容之后,最消耗性能的點(diǎn)就出現(xiàn)了:原數(shù)組中的數(shù)據(jù)必須重新計(jì)算其在新數(shù)組中的位置,并放進(jìn)去,這就是resize。
那么hashmap什么時(shí)候進(jìn)行擴(kuò)容呢?當(dāng)hashmap中的元素個(gè)數(shù)超過(guò)數(shù)組大小*loadFactor時(shí),就會(huì)進(jìn)行數(shù)組擴(kuò)容,loadFactor的默認(rèn)值為0.75,也就是說(shuō),默認(rèn)情況下,數(shù)組大小為16,那么當(dāng)hashmap中元素個(gè)數(shù)超過(guò)16*0.75=12的時(shí)候,就把數(shù)組的大小擴(kuò)展為2*16=32,即擴(kuò)大一倍,然后重新計(jì)算每個(gè)元素在數(shù)組中的位置,而這是一個(gè)非常消耗性能的操作,所以如果我們已經(jīng)預(yù)知hashmap中元素的個(gè)數(shù),那么預(yù)設(shè)元素的個(gè)數(shù)能夠有效的提高h(yuǎn)ashmap的性能。比如說(shuō),我們有1000個(gè)元素new HashMap(1000), 但是理論上來(lái)講new HashMap(1024)更合適,不過(guò)上面annegu已經(jīng)說(shuō)過(guò),即使是1000,hashmap也自動(dòng)會(huì)將其設(shè)置為1024。 但是new HashMap(1024)還不是更合適的,因?yàn)?.75*1000 < 1000, 也就是說(shuō)為了讓0.75 * size > 1000, 我們必須這樣new HashMap(2048)才最合適,既考慮了&的問(wèn)題,也避免了resize的問(wèn)題。
總結(jié):HashMap的實(shí)現(xiàn)原理:
總結(jié)
以上是生活随笔為你收集整理的esp8266接收到的数据如何存放到数组中_Java中HashMap的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《头号玩家》中的触感服装要来了?苹果正开
- 下一篇: jedis使用_Redis --Java