Map、HashMap、TreeMap、LinkedHashMap
Map
與Set接口一樣,Map也是一個(gè)接口不能實(shí)例化
Map是一種鍵(key)-值(value)對集合,Map中的每個(gè)元素都是一個(gè)鍵值對,其中key只能有一個(gè)為null且key不能重復(fù)(唯一),而value可以有多個(gè)為null且value可以重復(fù)(不唯一),當(dāng)key值重復(fù)寫入時(shí),新寫入的value值會覆蓋原有的值。
Map提供的是一種映射關(guān)系,能夠?qū)崿F(xiàn)通過key快速的查找value
HashMap
- 底層數(shù)據(jù)結(jié)構(gòu):數(shù)組+鏈表+紅黑樹實(shí)現(xiàn)(JDK1.8之后,JDK1.8之前是數(shù)組+鏈表):具體實(shí)現(xiàn)見底部:-)(哈希表)
- 是否有序:無序
- 是否排序:否
- 是否允許存入null:允許:且null作為key時(shí),總是存儲在hashtable第一個(gè)節(jié)點(diǎn)上,但最好不要這樣用,出現(xiàn)問題的話,排查比較麻煩。
- 是否線程安全:線程不安全
- 如何解決線程不安全的問題:(1).多線程環(huán)境中推薦使用ConcurrentHashMap (2).在多線程環(huán)境下若使用HashMap需要使用Collections.synchronizedMap()方法來獲取一個(gè)線程安全的集合
來,上個(gè)代碼
測試結(jié)果
直接輸出: {null=123, 張三豐=12, 張無忌=null, 張捕頭=null} 使用迭代器輸出: key:null value:123--key:張三豐 value:12--key:張無忌 value:null--key:張捕頭 value:null-- keySet遍歷key: key:null value:123--key:張三豐 value:12--key:張無忌 value:null--key:張捕頭 value:null-- 不使用迭代器,直接使用Entry輸出: key:null value:123--key:張三豐 value:12--key:張無忌 value:null--key:張捕頭 value:null--TreeMap
- 底層數(shù)據(jù)結(jié)構(gòu):紅黑樹
- 是否有序:無序
- 是否排序:是,按照key排序
- key能否為null:不能,因?yàn)闆]有null的比較方法,但value可以為null
- 是否線程安全:線程不安全
基于紅黑樹實(shí)現(xiàn)。TreeMap沒有調(diào)優(yōu)選項(xiàng),因?yàn)樵摌淇偺幱谄胶鉅顟B(tài)。
基于紅黑二叉樹的NavigableMap的實(shí)現(xiàn),存入TreeMap的元素應(yīng)當(dāng)實(shí)現(xiàn)Comparable接口或者實(shí)現(xiàn)Comparator接口,會按照排序后的順序迭代元素,兩個(gè)相比較的key不得拋出classCastException。主要用于存入元素的時(shí)候?qū)υ剡M(jìn)行自動排序,迭代輸出的時(shí)候就按排序順序輸出
來個(gè)代碼
再加盤代碼吧
public static void main(String[] args) {TreeMap<Integer, String> tm=new TreeMap<>();tm.put(3, "王");tm.put(1, "孟");tm.put(2, "李");System.out.println("直接輸出"+tm);System.out.println("firstKey():"+"key="+tm.firstKey()+" value="+tm.get(tm.firstKey()));System.out.println("higherKey():"+"key="+tm.higherKey(tm.firstKey())+" value="+tm.get(tm.higherKey(tm.firstKey())));System.out.println("subMap():"+tm.subMap(1, 3));System.out.println("subMap():"+tm.subMap(1, false, 3, true));System.out.println("ceilingKey():"+tm.ceilingKey(2));System.out.println("ceilingEntry():"+tm.ceilingEntry(2));System.out.println("higherEntry():"+tm.higherEntry(2));System.out.println("headMap():"+tm.headMap(2));//排序后,輸出從firstkey開始到secondkey之間的所有key-value對,不包括secondkey;System.out.println("headMap():"+tm.headMap(2,true));System.out.println("tailMap():"+tm.tailMap(2));System.out.println("tailMap():"+tm.tailMap(2, false));//若不加第二個(gè)參數(shù),默認(rèn)為true;System.out.println("descendingKeySet():"+tm.descendingKeySet());//倒序輸出TreeMap的key值System.out.println("descendingMap():"+tm.descendingMap());//倒序輸出TreeMap的key-valueSystem.out.println("containsKey():"+tm.containsKey(2));System.out.println("containsValue():"+tm.containsValue("李"));} 直接輸出{1=孟, 2=李, 3=王} firstKey():key=1 value=孟 higherKey():key=2 value=李 subMap():{1=孟, 2=李} subMap():{2=李, 3=王} ceilingKey():2 ceilingEntry():2=李 higherEntry():3=王 headMap():{1=孟} headMap():{1=孟, 2=李} tailMap():{2=李, 3=王} tailMap():{3=王} descendingKeySet():[3, 2, 1] descendingMap():{3=王, 2=李, 1=孟} containsKey():true containsValue()truekey=iter.next();可以獲取key的值
分析:雖然使用keyset及entryset來進(jìn)行遍歷能取得相同的結(jié)果,
但兩者的遍歷速度是有差別的。
keySet():迭代后只能通過get()取key;再根據(jù)key值取value。
entrySet():迭代后可以e.getKey(),e.getValue()取key和value。
說明:keySet()的速度比entrySet()慢了很多,也就是keySet方式遍歷Map的性能不如entrySet性能好
為了提高性能,以后多考慮用entrySet()方式來進(jìn)行遍歷。
LinkedHashMap
- 底層數(shù)據(jù)結(jié)構(gòu):雙向鏈表
- 是否有序:有序
- 是否排序:不排序
- 是否允許鍵值對為空:允許有一個(gè)鍵為空,多個(gè)值為空
- 是否線程安全:線程不安全
- 是否允許重復(fù)的key值:不允許,會覆蓋掉原key的value
上個(gè)線程代碼吧
public static void main(String[] args) {LinkedHashMap<String, String> lhm=new LinkedHashMap<>();for (int i = 0; i <=30; i++) {new Thread(()->{lhm.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));System.out.println(lhm);},String.valueOf(i)).start();}}測試結(jié)果
java.util.ConcurrentModificationExceptionHashTable
- 底層數(shù)據(jù)結(jié)構(gòu):JDK1.8之前是數(shù)組+鏈表,JDK1.8之后是數(shù)組+鏈表+紅黑樹
- 是否有序:無序
- 是否排序:否
- 是否線程安全:是
- 是否允許鍵值對為null:不允許
- 是否允許有重復(fù)的元素:不允許,重復(fù)的key所對應(yīng)的value會覆蓋原本的value。
從源碼上看:
HashTable繼承Dictionary,實(shí)現(xiàn)了Map接口
照例來盤代碼:
又好像沒什么必要吧,畢竟用法和HashMap一樣,多了個(gè)線程安全
數(shù)組+鏈表+紅黑樹:
如下圖所示,就是這樣滴:
寫一些大致過程吧:以HashMap<String,Integer>為例:首先,你開始了put("張",1);
而put的源碼是:
這里要注意hash(key)是計(jì)算你傳入的key的哈希值,然后調(diào)用putVal()函數(shù)開始存儲。
進(jìn)入putVal()后會經(jīng)過一系列判斷;比如新加入的key的HashCode值所對應(yīng)的位置在Entry數(shù)組中是否已經(jīng)存儲了別的值。如果沒有,直接寫入。如果有,則判斷key是否相等,不相等的話再繼續(xù)判斷next是否有值,有值的話,還需要知道是紅黑樹還是鏈表,然后再根據(jù)各自的方法判斷添加。如果是鏈表,還需要在添加完成后判斷是否需要轉(zhuǎn)成紅黑樹。如果上面這些都沒有,而是直接添加在Entry數(shù)組中的話,需要判斷一下是否要擴(kuò)容。
具體源碼分析可以看這篇博客:href="https://blog.csdn.net/m0_37914588/article/details/82287191
總結(jié)
以上是生活随笔為你收集整理的Map、HashMap、TreeMap、LinkedHashMap的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Set 、HashSet、TreeSet
- 下一篇: List、Vector、ArraryLi