Java Map接口详解
Map接口
Map接口概述
- Map與Collection并列存在。用于保存具有映射關(guān)系的數(shù)據(jù):key-value
- Map 中的 key 和 value 都可以是任何引用類型的數(shù)據(jù)
- Map 中的 key 用Set來(lái)存放,不允許重復(fù),即同一個(gè) Map 對(duì)象所對(duì)應(yīng)的類,須重寫(xiě)hashCode()和equals()方法
- 常用String類作為Map的“鍵”
- key 和 value 之間存在單向一對(duì)一關(guān)系,即通過(guò)指定的 key 總能找到唯一的、確定的 value
- Map接口的常用實(shí)現(xiàn)類:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用頻率最高的實(shí)現(xiàn)類
Map接口常用方法
添加、刪除、修改操作:
-
Object put(Object key,Object value):將指定key-value添加到(或修改)當(dāng)前map對(duì)象中
-
void putAll(Map m):將m中的所有key-value對(duì)存放到當(dāng)前map中
-
Object remove(Object key):移除指定key的key-value對(duì),并返回value
-
void clear():清空當(dāng)前map中的所有數(shù)據(jù)
元素查詢的操作: -
Object get(Object key):獲取指定key對(duì)應(yīng)的value
-
boolean containsKey(Object key):是否包含指定的key
-
boolean containsValue(Object value):是否包含指定的value
-
int size():返回map中key-value對(duì)的個(gè)數(shù)
-
boolean isEmpty():判斷當(dāng)前map是否為空
-
boolean equals(Object obj):判斷當(dāng)前map和參數(shù)對(duì)象obj是否相等
元視圖操作的方法: -
Set keySet():返回所有key構(gòu)成的Set集合
-
Collection values():返回所有value構(gòu)成的Collection集合
-
Set entrySet():返回所有key-value對(duì)構(gòu)成的Set集合
Map實(shí)現(xiàn)類之一:HashMap
- HashMap是 Map 接口使用頻率最高的實(shí)現(xiàn)類。
- 允許使用null鍵和null值,與HashSet一樣,不保證映射的順序。
- 所有的key構(gòu)成的集合是Set:無(wú)序的、不可重復(fù)的。所以,key所在的類要重寫(xiě):equals()和hashCode()
- 所有的value構(gòu)成的集合是Collection:無(wú)序的、可以重復(fù)的。所以,value所在的類要重寫(xiě):equals()
- 一個(gè)key-value構(gòu)成一個(gè)entry
- 所有的entry構(gòu)成的集合是Set:無(wú)序的、不可重復(fù)的
- HashMap 判斷兩個(gè) key 相等的標(biāo)準(zhǔn)是:兩個(gè) key 通過(guò) equals() 方法返回 true,hashCode 值也相等。
- HashMap 判斷兩個(gè) value相等的標(biāo)準(zhǔn)是:兩個(gè) value 通過(guò) equals() 方法返回 true。
HashMap的存儲(chǔ)結(jié)構(gòu)
JDK 7及以前版本:HashMap是數(shù)組+鏈表結(jié)構(gòu)(即為鏈地址法)
JDK 8版本發(fā)布以后:HashMap是數(shù)組+鏈表+紅黑樹(shù)實(shí)現(xiàn)。
JDK1.8之前
- HashMap的內(nèi)部存儲(chǔ)結(jié)構(gòu)其實(shí)是數(shù)組和鏈表的結(jié)合。當(dāng)實(shí)例化一個(gè)HashMap時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)長(zhǎng)度為Capacity的Entry數(shù)組,這個(gè)長(zhǎng)度在哈希表中被稱為容量(Capacity),在這個(gè)數(shù)組中可以存放元素的位置我們稱之為“桶”(bucket),每個(gè)bucket都有自己的索引,系統(tǒng)可以根據(jù)索引快速的查找bucket中的元素。
- 每個(gè)bucket中存儲(chǔ)一個(gè)元素,即一個(gè)Entry對(duì)象,但每一個(gè)Entry對(duì)象可以帶一個(gè)引用變量,用于指向下一個(gè)元素,因此,在一個(gè)桶中,就有可能生成一個(gè)Entry鏈。而且新添加的元素作為鏈表的head。
添加元素的過(guò)程:
向HashMap中添加entry1(key,value),需要首先計(jì)算entry1中key的哈希值(根據(jù)key所在類的hashCode()計(jì)算得到),此哈希值經(jīng)過(guò)處理以后,得到在底層Entry[]數(shù)組中要存儲(chǔ)的位置i。如果位置i上沒(méi)有元素,則entry1直接添加成功。如果位置i上已經(jīng)存在entry2(或還有鏈表存在的entry3,entry4),則需要通過(guò)循環(huán)的方法,依次比較entry1中key和其他的entry。如果彼此hash值不同,則直接添加成功。如果hash值不同,繼續(xù)比較二者是否equals。如果返回值為true,則使用entry1的value去替換equals為true的entry的value。如果遍歷一遍以后,發(fā)現(xiàn)所有的equals返回都為false,則entry1仍可添加成功。entry1指向原有的entry元素。
HashMap的擴(kuò)容
當(dāng)HashMap中的元素越來(lái)越多的時(shí)候,hash沖突的幾率也就越來(lái)越高,因?yàn)閿?shù)組的長(zhǎng)度是固定的。所以為了提高查詢的效率,就要對(duì)HashMap的數(shù)組進(jìn)行擴(kuò)容,而在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ù)組大小(數(shù)組總大小length,不是數(shù)組中個(gè)數(shù)
size)loadFactor 時(shí) , 就 會(huì) 進(jìn) 行 數(shù) 組 擴(kuò) 容 , loadFactor 的默認(rèn) 值(DEFAULT_LOAD_FACTOR)為0.75,這是一個(gè)折中的取值。也就是說(shuō),默認(rèn)情況下,數(shù)組大小(DEFAULT_INITIAL_CAPACITY)為16,那么當(dāng)HashMap中元素個(gè)數(shù)超過(guò)160.75=12(這個(gè)值就是代碼中的threshold值,也叫做臨界值)的時(shí)候,就把數(shù)組的大小擴(kuò)展為 2*16=32,即擴(kuò)大一倍,然后重新計(jì)算每個(gè)元素在數(shù)組中的位置,而這是一個(gè)非常消耗性能的操作,所以如果我們已經(jīng)預(yù)知HashMap中元素的個(gè)數(shù),那么預(yù)設(shè)元素的個(gè)數(shù)能夠有效的提高HashMap的性能。
JDK1.8
- HashMap的內(nèi)部存儲(chǔ)結(jié)構(gòu)其實(shí)是數(shù)組+鏈表+樹(shù)的結(jié)合。當(dāng)實(shí)例化一個(gè)HashMap時(shí),會(huì)初始化initialCapacity和loadFactor,在put第一對(duì)映射關(guān)系時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)長(zhǎng)度為initialCapacity的Node數(shù)組,這個(gè)長(zhǎng)度在哈希表中被稱為容量(Capacity),在這個(gè)數(shù)組中可以存放元素的位置我們稱之為“桶”(bucket),每個(gè)bucket都有自己的索引,系統(tǒng)可以根據(jù)索引快速的查找bucket中的元素。
- 每個(gè)bucket中存儲(chǔ)一個(gè)元素,即一個(gè)Node對(duì)象,但每一個(gè)Node對(duì)象可以帶一個(gè)引用變量next,用于指向下一個(gè)元素,因此,在一個(gè)桶中,就有可能生成一個(gè)Node鏈。也可能是一個(gè)一個(gè)TreeNode對(duì)象,每一個(gè)TreeNode對(duì)象可以有兩個(gè)葉子結(jié)點(diǎn)left和right,因此,在一個(gè)桶中,就有可能生成一個(gè)TreeNode樹(shù)。而新添加的元素作為鏈表的last,或樹(shù)的葉子結(jié)點(diǎn)。
那么HashMap什么時(shí)候進(jìn)行擴(kuò)容和樹(shù)形化呢?
當(dāng)HashMap中的元素個(gè)數(shù)超過(guò)數(shù)組大小(數(shù)組總大小length,不是數(shù)組中個(gè)數(shù)size)loadFactor 時(shí) , 就會(huì)進(jìn)行數(shù)組擴(kuò)容 , loadFactor 的默認(rèn) (DEFAULT_LOAD_FACTOR)為0.75,這是一個(gè)折中的取值。也就是說(shuō),默認(rèn)情況下,數(shù)組大(DEFAULT_INITIAL_CAPACITY)為16,那么當(dāng)HashMap中元素個(gè)數(shù)超過(guò)160.75=12(這個(gè)值就是代碼中的threshold值,也叫做臨界值)的時(shí)候,就把數(shù)組的大小擴(kuò)展為 2*16=32,即擴(kuò)大一倍,然后重新計(jì)算每個(gè)元素在數(shù)組中的位置,而這是一個(gè)非常消耗性能的操作,所以如果我們已經(jīng)預(yù)知HashMap中元素的個(gè)數(shù),那么預(yù)設(shè)元素的個(gè)數(shù)能夠有效的提高HashMap的性能。
當(dāng)HashMap中的其中一個(gè)鏈的對(duì)象個(gè)數(shù)如果達(dá)到了8個(gè),此時(shí)如果capacity沒(méi)有達(dá)到64,那么HashMap會(huì)先擴(kuò)容解決,如果已經(jīng)達(dá)到了64,那么這個(gè)鏈會(huì)變成樹(shù),結(jié)點(diǎn)類型由Node變成TreeNode類型。當(dāng)然,如果當(dāng)映射關(guān)系被移除后,下次resize方法時(shí)判斷樹(shù)的結(jié)點(diǎn)個(gè)數(shù)低于6個(gè),也會(huì)把樹(shù)再轉(zhuǎn)為鏈表。
總結(jié):JDK1.8相較于之前的變化:
1 .HashMap map = new HashMap();//默認(rèn)情況下,先不創(chuàng)建長(zhǎng)度為16的數(shù)組
2 當(dāng)首次調(diào)用map.put()時(shí),再創(chuàng)建長(zhǎng)度為16的數(shù)組
3 數(shù)組為Node類型,在jdk7中稱為Entry類型
4 形成鏈表結(jié)構(gòu)時(shí),新添加的key-value對(duì)在鏈表的尾部(七上八下)
5 當(dāng)數(shù)組指定索引位置的鏈表長(zhǎng)度>8時(shí),且map中的數(shù)組的長(zhǎng)度> 64時(shí),此索引位置上的所有key-value對(duì)使用紅黑樹(shù)進(jìn)行存儲(chǔ)。
HashMap源碼中的重要常量
DEFAULT_INITIAL_CAPACITY : HashMap的默認(rèn)容量,16
MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
DEFAULT_LOAD_FACTOR:HashMap的默認(rèn)加載因子
TREEIFY_THRESHOLD:Bucket中鏈表長(zhǎng)度大于該默認(rèn)值,轉(zhuǎn)化為紅黑樹(shù)
UNTREEIFY_THRESHOLD:Bucket中紅黑樹(shù)存儲(chǔ)的Node小于該默認(rèn)值,轉(zhuǎn)化為鏈表
MIN_TREEIFY_CAPACITY:桶中的Node被樹(shù)化時(shí)最小的hash表容量。(當(dāng)桶中Node的
數(shù)量大到需要變紅黑樹(shù)時(shí),若hash表容量小于MIN_TREEIFY_CAPACITY時(shí),此時(shí)應(yīng)執(zhí)行
resize擴(kuò)容操作這個(gè)MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4
倍。)
table:存儲(chǔ)元素的數(shù)組,總是2的n次冪
entrySet:存儲(chǔ)具體元素的集
size:HashMap中存儲(chǔ)的鍵值對(duì)的數(shù)量
modCount:HashMap擴(kuò)容和結(jié)構(gòu)改變的次數(shù)。
threshold:擴(kuò)容的臨界值,=容量*填充因子
loadFactor:填充因子
Map實(shí)現(xiàn)類之二:LinkedHashMap
- LinkedHashMap 是 HashMap 的子類
- 在HashMap存儲(chǔ)結(jié)構(gòu)的基礎(chǔ)上,使用了一對(duì)雙向鏈表來(lái)記錄添加元素的順序
- 與LinkedHashSet類似,LinkedHashMap 可以維護(hù) Map 的迭代
順序:迭代順序與 Key-Value 對(duì)的插入順序一致
Map實(shí)現(xiàn)類之三:TreeMap
- TreeMap存儲(chǔ) Key-Value 對(duì)時(shí),需要根據(jù) key-value 對(duì)進(jìn)行排序。
TreeMap 可以保證所有的 Key-Value 對(duì)處于有序狀態(tài)。 - TreeSet底層使用紅黑樹(shù)結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)
- TreeMap 的 Key 的排序:
?自然排序:TreeMap 的所有的 Key 必須實(shí)現(xiàn) Comparable 接口,而且所有的 Key 應(yīng)該是同一個(gè)類的對(duì)象,否則將會(huì)拋出ClasssCastException
?定制排序:創(chuàng)建 TreeMap 時(shí),傳入一個(gè) Comparator 對(duì)象,該對(duì)象負(fù)責(zé)對(duì)TreeMap 中的所有 key 進(jìn)行排序。此時(shí)不需要 Map 的 Key 實(shí)現(xiàn)Comparable 接口 - TreeMap判斷兩個(gè)key相等的標(biāo)準(zhǔn):兩個(gè)key通過(guò)compareTo()方法或者compare()方法返回0。
Map實(shí)現(xiàn)類之四:Hashtable
- Hashtable是個(gè)古老的 Map 實(shí)現(xiàn)類,JDK1.0就提供了。不同于HashMap,Hashtable是線程安全的。
- Hashtable實(shí)現(xiàn)原理和HashMap相同,功能相同。底層都使用哈希表結(jié)構(gòu),查詢速度快,很多情況下可以互用。
- 與HashMap不同,Hashtable 不允許使用 null 作為 key 和 value
- 與HashMap一樣,Hashtable 也不能保證其中 Key-Value 對(duì)的順序
- Hashtable判斷兩個(gè)key相等、兩個(gè)value相等的標(biāo)準(zhǔn),與HashMap一致。
Map實(shí)現(xiàn)類之五:Properties
- Properties 類是 Hashtable 的子類,該對(duì)象用于處理屬性文件
- 由于屬性文件里的 key、value 都是字符串類型,所以 Properties 里的 key 和 value 都是字符串類型
- 存取數(shù)據(jù)時(shí),建議使用setProperty(String key,String value)方法和getProperty(String key)方法
總結(jié)
以上是生活随笔為你收集整理的Java Map接口详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: IDA Pro的patch插件 KeyP
- 下一篇: Java学习:多线程(2)