1.常用的實(shí)現(xiàn)類(lèi)結(jié)構(gòu)
一、HashMap
實(shí)現(xiàn)了Map、Cloneable、Serializable接口,繼承了AbstractMap類(lèi)
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
/**
* Map接口: 實(shí)現(xiàn)鍵值對(duì),Map接口規(guī)定了一個(gè)key對(duì)應(yīng)一個(gè)value
* HashMap使用該接口用來(lái)替換Dictionary類(lèi)
*
* AbstractMap類(lèi): 繼承Map的抽象類(lèi),減少M(fèi)ap操作的實(shí)現(xiàn)
*
* Cloneable接口: 可以顯示的調(diào)用Object.clone()方法,合法的對(duì)該類(lèi)
* 實(shí)例進(jìn)行字段復(fù)制
*
* Serializable接口: 實(shí)現(xiàn)該接口后,該類(lèi)可以被序列化和反序列化
*/
1.HashMap是否線程安全?
HashMap是線程不安全的,在并發(fā)的環(huán)境下可以使用ConcurrentHashMap。
2.HashMap的內(nèi)部實(shí)現(xiàn)
內(nèi)部實(shí)現(xiàn):在JDK1.8之前是數(shù)組+鏈表,JDK1.8之后是數(shù)組+鏈表+紅黑樹(shù)
加入紅黑樹(shù)的原因:JDK1.8之前HashMap使用的是數(shù)組加鏈表,由于哈希函數(shù)不能百分百的讓元素均勻的分布,就會(huì)造成有大量的元素存入同一個(gè)index(桶)下,這樣index就形成了一條很長(zhǎng)的鏈表,由此元素的遍歷的時(shí)間復(fù)雜度為O(n),失去了HashMap的優(yōu)勢(shì),加入了紅黑樹(shù),查找的時(shí)間復(fù)雜度為O(log n),實(shí)現(xiàn)了優(yōu)化
數(shù)組的特點(diǎn):查找快,時(shí)間復(fù)雜度是O(1),增刪慢,時(shí)間復(fù)雜度是O(n)
鏈表的特點(diǎn):查找慢,時(shí)間復(fù)雜度是O(n),增刪快,時(shí)間復(fù)雜度是O(1)
紅黑樹(shù)的特點(diǎn):在遍歷鏈表的基礎(chǔ)上,紅黑樹(shù)查找的時(shí)間復(fù)雜度是O(log n)
紅黑樹(shù)的查詢效率遠(yuǎn)大于鏈表,但是插入/刪除要比鏈表慢
3.HashMap的主要參數(shù)
1.默認(rèn)初始容量:16,必須是2的整數(shù)次方
2.默認(rèn)加載因子:0.75
3.閾值:容量*加載因子
4.樹(shù)形閾值:默認(rèn)是8,當(dāng)bucket中的鏈表長(zhǎng)度大于8時(shí),則進(jìn)行鏈表樹(shù)化
5.非樹(shù)形閾值:默認(rèn)是6,當(dāng)進(jìn)行擴(kuò)容時(shí),當(dāng)進(jìn)行擴(kuò)容(resize())時(shí)(這時(shí)候的HashMap的數(shù)據(jù)存儲(chǔ)位置會(huì)重新計(jì)算),在計(jì)算完后,原有的紅黑樹(shù)內(nèi)的數(shù)量<6時(shí),則由紅黑樹(shù)轉(zhuǎn)換為鏈表
6.樹(shù)形最小容量:桶可能是樹(shù)的哈希表的最小容量,至少是TREEIFY_THRESHOLD 的 4 倍,這樣能避免擴(kuò)容時(shí)的沖突
//鏈表轉(zhuǎn)紅黑樹(shù)的閾值
static final int TREEIFY_THRESHOLD = 8;
//紅黑樹(shù)轉(zhuǎn)鏈表的閾值
static final int UNTREEIFY_THRESHOLD = 6;
/**
*最小樹(shù)形化容量閾值:即 當(dāng)哈希表中的容量 > 該值時(shí),才允許樹(shù)形化鏈表 (即 將鏈表 轉(zhuǎn)換成紅黑樹(shù))
*否則,若桶內(nèi)元素太多時(shí),則直接擴(kuò)容,而不是樹(shù)形化
*為了避免進(jìn)行擴(kuò)容、樹(shù)形化選擇的沖突,這個(gè)值不能小于 4 * TREEIFY_THRESHOLD
**/
static final int MIN_TREEIFY_CAPACITY = 64;
只有在數(shù)組的長(zhǎng)度大于64時(shí),且鏈表的長(zhǎng)度>8時(shí),才能樹(shù)形化鏈表
鏈表的長(zhǎng)度大于8時(shí)會(huì)調(diào)用treeifyBin方法轉(zhuǎn)換為紅黑樹(shù),但是treeifyBin方法內(nèi)部有一個(gè)判斷,當(dāng)只有數(shù)組的長(zhǎng)度>64的時(shí)候,才能將鏈表樹(shù)形化,否則只進(jìn)行resize擴(kuò)容
因?yàn)殒湵磉^(guò)長(zhǎng)而數(shù)組過(guò)短,會(huì)經(jīng)常發(fā)生hash碰撞,這時(shí)進(jìn)行樹(shù)形化的作用不大,因?yàn)殒湵磉^(guò)長(zhǎng)的原因就是數(shù)組過(guò)短。樹(shù)形化之前要檢查數(shù)組的長(zhǎng)度,<64進(jìn)行擴(kuò)容,而不是進(jìn)行樹(shù)形化
鏈表的長(zhǎng)度>8,但數(shù)組的長(zhǎng)度<64時(shí),不會(huì)進(jìn)行樹(shù)形化,而是進(jìn)行resize后rehash重新排序
4.HashMap的常用方法
添加:V put(K key,V value) -->添加元素(也可以實(shí)現(xiàn)修改)
刪除:void clear() -->清空所有鍵值對(duì)元素
? V remove(Object key) -->根據(jù)鍵刪除對(duì)應(yīng)的值,并把值返回
判斷:containsKey(Object key) -->是否包含指定的鍵
? containsValue(Object value)–>是否包含指定的值
遍歷:Set<Map.Entry<K,V>> entrySet()–>獲取鍵值對(duì)
? V get(Object key) -->根據(jù)鍵獲取值
? Collection value()–>獲取值的集合
獲?。篠et setKey()–>獲取鍵的集合
? int size()–>獲取集合元素的個(gè)數(shù)
基本方法的使用
HashMap<Integer,String> map=new HashMap<>();
//添加元素
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");
//鍵不可重復(fù),值被覆蓋
map.put(3, "C");
//通過(guò)鍵刪除整個(gè)鍵值對(duì)
map.remove(3);
//清空
map.clear();
//是否為空
System.out.println(map.isEmpty());//false
//是否包含4
System.out.println(map.containsKey(4));//false
//是否包含“b”值
System.out.println(map.containsValue("b"));//true
//獲取集合元素個(gè)數(shù)
System.out.println(map.size());//3
//通過(guò)鍵獲取值
System.out.println(map.get(3));//C
//獲取所有值構(gòu)成的集合
Collection<String> values=map.values();
for(String v:values){
System.out.println("值:"+v);//值:a 值:b 值:c
}
System.out.println(map);
}
兩種遍歷方式
public static void main(String[] args) {
Map<String,Integer> map=new HashMap<>();
map.put("小林",21);
map.put("小張",35);
map.put("小王",18);
demo1(map);
demo2(map);
}
//通過(guò)Set<K> setKey()方法遍歷
private static void demo1(Map<String,Integer> map) {
Set<String> keys=map.keySet();
for (String key:keys){
Integer value=map.get(key);
System.out.println("鍵為:"+key+"值為:"+value);//鍵為:小林值為:21
//鍵為:小王值為:18
//鍵為:小張值為:35
}
}
//通過(guò)Set<Map.Entry<K,V>> entrySet()方法遍歷
private static void demo2(Map<String, Integer> map) {
Set<Map.Entry<String,Integer>> kvs=map.entrySet();
for (Map.Entry<String,Integer> kv:kvs){
String kstring=kv.getKey();
Integer istring=kv.getValue();
System.out.println(kstring+"-----"+istring);
//小林-----21
//小王-----18
//小張-----35
}
}
5.關(guān)于hash沖突問(wèn)題
1.原因:
? 當(dāng)對(duì)某個(gè)元素進(jìn)行哈希運(yùn)算后,得到一個(gè)存儲(chǔ)地址,在進(jìn)行插入的時(shí)候,發(fā)現(xiàn)已經(jīng)被其他元素占用了。這就是所謂的哈希沖突,也叫哈希碰撞。
哈希函數(shù)的設(shè)計(jì)至關(guān)重要,好的哈希函數(shù)會(huì)盡量的確保計(jì)算簡(jiǎn)單和散列地址分布均勻,但是,數(shù)組是一塊連續(xù)的固定的長(zhǎng)度的內(nèi)存空間,再好的哈希函數(shù)也不能保證得到的存儲(chǔ)地址絕對(duì)不發(fā)生沖突。
2.解決hash沖突的方法:
開(kāi)放地址法:發(fā)生沖突,繼續(xù)尋找下一塊未被占用的存儲(chǔ)地址。
再散列函數(shù)法:當(dāng)發(fā)生沖突,使用第二個(gè)、第三個(gè)、哈希函數(shù)計(jì)算地址,直到無(wú)沖突。
鏈地址法:將所有關(guān)鍵字的同義詞的記錄存儲(chǔ)在一個(gè)單鏈表中,我們稱(chēng)這種表為同義詞子表。
HashMap采用的是鏈地址法,也就是數(shù)組+鏈表的方式
HashMap由數(shù)組+鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的,如果定位到的數(shù)組位置不含鏈表(當(dāng)前的entry的next指向null),那么對(duì)于查找,添加等操作很快,僅需一次尋址即可;如果定位到的數(shù)組包含鏈表,對(duì)于添加操作,其時(shí)間復(fù)雜度為O(n),首先遍歷鏈表,存在即覆蓋,否則新增,然后通過(guò)key對(duì)象的equals方法逐一比對(duì)查找。所以!對(duì)于性能考慮,HashMap中的鏈表出現(xiàn)越少,性能才會(huì)越好。
6.JDK1.8之前HashMap存在死循環(huán)的問(wèn)題
原因:由于數(shù)組擴(kuò)容后,同一索引位置的節(jié)點(diǎn)順序會(huì)反掉
7.HashMap和Hashtable的區(qū)別

8.重寫(xiě)equals()方法后,是否一定要重寫(xiě)hashCode()方法?為什么?
重寫(xiě)equals()方法,需要重寫(xiě)hashCode()方法。
hashCode規(guī)定:兩個(gè)對(duì)象相等(即equals()返回true),hashCode一定相同;兩個(gè)對(duì)象hashCode相同,兩個(gè)對(duì)象不一定相等;
重寫(xiě)equals,而不重寫(xiě)hashCode方法,默認(rèn)調(diào)用的是Object類(lèi)的hashCode()方法,會(huì)導(dǎo)致兩個(gè)對(duì)象的equals相同但hashCode不同。簡(jiǎn)單的來(lái)說(shuō),重寫(xiě)equals方法后,重寫(xiě)hashCode方法就是為了確保比較的兩個(gè)對(duì)象是同一對(duì)象。
二、LinkedHashMap
LinkedHashMap的底層結(jié)構(gòu)和HashMap是相同的,因?yàn)長(zhǎng)inkedHashMap繼承了HashMap,
區(qū)別在于:LinkedHashMap內(nèi)部提供了Entry,替換了HashMap中的Node。
LinkedHashMap:保證在遍歷map元素時(shí),可以照添加的順序?qū)崿F(xiàn)遍歷
原因:在原來(lái)的HashMap底層結(jié)構(gòu)的基礎(chǔ)上,增加了一對(duì)指針,指向前一個(gè)和后一個(gè)元素
對(duì)于頻繁的遍歷操作,LinkedHashMap的效率要高于HashMap
三、TreeMap
保證照key-value對(duì)進(jìn)行排序,實(shí)現(xiàn)排序遍歷,此時(shí)考慮key的自然排序或者定制排序,底層使用的是紅黑樹(shù)
元素根據(jù)鍵排序,元素具有唯一性
1)自然排序
讓元素所在的類(lèi)繼承自然排序Comparable
2)比較器排序
讓集合的構(gòu)造方法接收一個(gè)比較器接口(Comparator的實(shí)現(xiàn)對(duì)象)
四、Hashtable
作為古老的實(shí)現(xiàn)類(lèi);線程安全的,效率低;不能存儲(chǔ)null的key和value
最后
感謝你看到這里,看完有什么的不懂的可以在評(píng)論區(qū)問(wèn)我,覺(jué)得文章對(duì)你有幫助的話記得給我點(diǎn)個(gè)贊,每天都會(huì)分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章!