日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Map,HashMap,TreeMap

發布時間:2025/4/16 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Map,HashMap,TreeMap 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. Map
Map接口中,鍵和值一一映射,可以通過鍵來獲取值。
在數組中我們是通過數組下標來對其內容索引的,而在Map中我們通過對象來對對象進行索引,用來索引的對象叫做key,其對應的對象叫做value。這就是我們平時說的鍵值對。

  • 給定一個鍵和一個值,你可以將該值存儲在一個Map對象. 之后,你可以通過鍵來訪問對應的值。
  • 當訪問的值不存在的時候,方法就會拋出一個NoSuchElementException異常.
  • 當對象的類型和Map里元素類型不兼容的時候,就會拋出一個 ClassCastException異常。
  • 當在不允許使用Null對象的Map中使用Null對象,會拋出一個NullPointerException 異常。
  • 當嘗試修改一個只讀的Map時,會拋出一個UnsupportedOperationException異常。
  • Map接口的方法

    序號 方法描述
    1 void clear( )
    ?從此映射中移除所有映射關系(可選操作)。
    2 boolean containsKey(Object k)
    如果此映射包含指定鍵的映射關系,則返回?true。
    3 boolean containsValue(Object v)
    如果此映射將一個或多個鍵映射到指定值,則返回?true。
    4 Set entrySet( )
    返回此映射中包含的映射關系的?Set 視圖。
    5 boolean equals(Object obj)
    比較指定的對象與此映射是否相等。
    6 Object get(Object k)
    返回指定鍵所映射的值;如果此映射不包含該鍵的映射關系,則返回?null。
    7 int hashCode( )
    返回此映射的哈希碼值。
    8 boolean isEmpty( )
    如果此映射未包含鍵-值映射關系,則返回?true。
    9 Set keySet( )
    返回此映射中包含的鍵的?Set 視圖。
    10 Object put(Object k, Object v)
    將指定的值與此映射中的指定鍵關聯(可選操作)。
    11 void putAll(Map m)
    從指定映射中將所有映射關系復制到此映射中(可選操作)。
    12 Object remove(Object k)
    如果存在一個鍵的映射關系,則將其從此映射中移除(可選操作)。
    13 int size( )
    返回此映射中的鍵-值映射關系數。
    14 Collection values( )
    返回此映射中包含的值的 Collection 視圖。

    下面的關于HashMap的原文地址:https://baijiahao.baidu.com/s?id=1618550070727689060&wfr=spider&for=pc
    2. HashMap

  • HashMap是一個散列桶(數組和鏈表),它存儲的內容是鍵值對(key-value)映射
  • 最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值,
  • HashMap最多只允許一條記錄的鍵為Null(多條會覆蓋);允許多條記錄的值為 Null;允許鍵和值同時為空。
  • 因為HashMap是非同步(非synchronized)的,所以具有很快的訪問速度。
  • HashMap采用了數組和鏈表的數據結構,能在查詢和修改方便繼承了數組的線性查找和鏈表的尋址修改
  • key的hash值是先計算key的hashcode值,然后再進行計算,每次容量擴容會重新計算所以key的hash值,會消耗資源,要求key必須重寫equals和hashcode方法
  • 默認初始容量16,加載因子0.75,擴容為舊容量乘2,查找元素快,如果key一樣則比較value,如果value不一樣,則按照鏈表結構存儲value,就是一個key后面有多個value;
  • HashMap的工作原理
    HashMap是基于hashing的原理,我們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當我們給put()方法傳遞鍵和值時,我們先對鍵調用hashCode()方法,計算并返回的hashCode是用于找到Map數組的bucket位置來儲存Node 對象。這里關鍵點在于指出,HashMap是在bucket中儲存鍵對象和值對象,作為Map.Node 。

    a. 以下是HashMap初始化 ,簡單模擬數據結構
    Node[] table=new Node[16] 散列桶初始化,tableclass Node { hash;//hash值 key;//鍵 value;//值 node next;//用于指向鏈表的下一層(產生沖突,用拉鏈法)}

    b.以下是具體的put過程(JDK1.8版)

  • 對Key求Hash值,然后再計算下標

  • 如果沒有碰撞,直接放入桶中(碰撞的意思是計算得到的Hash值相同,需要放到同一個bucket中)

  • 如果碰撞了,以鏈表的方式鏈接到后面

  • 如果鏈表長度超過閥值( TREEIFY THRESHOLD==8),就把鏈表轉成紅黑樹,鏈表長度低于6,就把紅黑樹轉回鏈表

  • 如果節點已經存在就替換舊值

  • 如果桶滿了(容量16*加載因子0.75),就需要 resize(擴容2倍后重排)

  • c. 以下是具體get過程(考慮特殊情況如果兩個鍵的hashcode相同,你如何獲取值對象?)
    當我們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,找到bucket位置之后,會調用keys.equals()方法去找到鏈表中正確的節點,最終找到要找的值對象。


    有什么方法可以減少碰撞?

  • 擾動函數可以減少碰撞,原理是如果兩個不相等的對象返回不同的hashcode的話,那么碰撞的幾率就會小些,這就意味著存鏈表結構減小,這樣取值的話就不會頻繁調用equal方法,這樣就能提高HashMap的性能。(擾動即Hash方法內部的算法實現,目的是讓不同對象返回不同hashcode。)
  • 使用不可變的、聲明作final的對象,并且采用合適的equals()和hashCode()方法的話,將會減少碰撞的發生。不可變性使得能夠緩存不同鍵的hashcode,這將提高整個獲取對象的速度,使用String,Interger這樣的wrapper類作為鍵是非常好的選擇。為什么String, Interger這樣的wrapper類適合作為鍵?因為String是final的,而且已經重寫了equals()和hashCode()方法了。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值在放入時和獲取時返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對象。
  • HashMap中hash函數怎么是是實現的?
    我們可以看到在hashmap中要找到某個元素,需要根據key的hash值來求得對應數組中的位置。如何計算這個位置就是hash算法。前面說過hashmap的數據結構是數組和鏈表的結合,所以我們當然希望這個hashmap里面的元素位置盡量的分布均勻些,盡量使得每個位置上的元素數量只有一個,那么當我們用hash算法求得這個位置的時候,馬上就可以知道對應位置的元素就是我們要的,而不用再去遍歷鏈表。 所以我們首先想到的就是把hashcode對數組長度取模運算,這樣一來,元素的分布相對來說是比較均勻的。但是,“模”運算的消耗還是比較大的,能不能找一種更快速,消耗更小的方式,我們來看看JDK1.8的源碼是怎么做的(被樓主修飾了一下)

    static final int hash(Object key){if (key == null){ return 0; }int h;h=key.hashCode();return (n-1)&(h ^ (h >>> 16));}

    返回散列值也就是hashcode // ^ :按位異或 // >>>:無符號右移,忽略符號位,空位都以0補齊 //其中n是數組的長度,即Map的數組部分初始化長度 return (n-1)&(h ^ (h >>> 16));}

    簡單來說就是

    1、高16bt不變,低16bit和高16bit做了一個異或(得到的HASHCODE轉化為32位的二進制,前16位和后16位低16bit和高16bit做了一個異或)

    2、(n·1)&hash=->得到下標

    如果HashMap的大小超過了負載因子(load factor)定義的容量,怎么辦?

    默認的負載因子大小為0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會創建原來HashMap大小的兩倍的bucket數組,來重新調整map的大小,并將原來的對象放入新的bucket數組中。這個過程叫作rehashing,因為它調用hash方法找到新的bucket位置。這個值只可能在兩個地方,一個是原下標的位置,另一種是在下標為<原下標+原容量>的位置

    重新調整HashMap大小存在什么問題嗎?

    當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個線程都發現HashMap需要重新調整大小了,它們會同時試著調整大小。在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap并不會將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那么就死循環了。(多線程的環境下不使用HashMap)

    為什么多線程會導致死循環,它是怎么發生的?
    HashMap的容量是有限的。當經過多次元素插入,使得HashMap達到一定飽和度時,Key映射位置發生沖突的幾率會逐漸提高。這時候,HashMap需要擴展它的長度,也就是進行Resize。1.擴容:創建一個新的Entry空數組,長度是原數組的2倍。2.ReHash:遍歷原Entry數組,把所有的Entry重新Hash到新數組。

    HashMap為什么是線程不安全的?
    HashMap 在并發時可能出現的問題主要是兩方面:

  • put的時候導致的多線程數據不一致
    比如有兩個線程A和B,首先A希望插入一個key-value對到HashMap中,首先計算記錄所要落到的 hash桶的索引坐標,然后獲取到該桶里面的鏈表頭結點,此時線程A的時間片用完了,而此時線程B被調度得以執行,和線程A一樣執行,只不過線程B成功將記錄插到了桶里面,假設線程A插入的記錄計算出來的 hash桶索引和線程B要插入的記錄計算出來的 hash桶索引是一樣的,那么當線程B成功插入之后,線程A再次被調度運行時,它依然持有過期的鏈表頭但是它對此一無所知,以至于它認為它應該這樣做,如此一來就覆蓋了線程B插入的記錄,這樣線程B插入的記錄就憑空消失了,造成了數據不一致的行為。
  • resize而引起死循環
    這種情況發生在HashMap自動擴容時,當2個線程同時檢測到元素個數超過 數組大小 × 負載因子。此時2個線程會在put()方法中調用了resize(),兩個線程同時修改一個鏈表結構會產生一個循環鏈表(JDK1.7中,會出現resize前后元素順序倒置的情況)。接下來再想通過get()獲取某一個元素,就會出現死循環。
  • 作者:小北覓
    鏈接:https://www.jianshu.com/p/ee0de4c99f87

    HashMap的三種遍歷方法:

    package HashMap_demo;import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry;public class HashMap_demo {public static void main(String[] args) {//Map<Integer, String> map=new HashMap<>();//這個和底下的一個寫法都可以。Map<Integer, String> map=new HashMap<Integer, String>();map.put(1, "A");map.put(8, "D");map.put(3, "C");map.put(4, "D");map.put(3, "E");map.put(2, "");map.put(null, "C");//通過Map中的keySet()方法遍歷獲得key值進行輸出//看別的大佬都管這叫增強for循環遍歷System.out.println("通過Map中的keySet()方法遍歷獲得key值進行輸出:");for(Integer s:map.keySet()) {System.out.print("Key="+s+":");System.out.print("value="+map.get(s)+"--");}//通過Map中的keySet()方法與valueSet()方法遍歷獲得key值和value值進行輸出System.out.println("\n"+"通過Map中的keySet()方法與valueSet()方法遍歷獲得key值和value值進行輸出");for(Integer s:map.keySet()) {System.out.print("Key="+s+":");}for(String v:map.values()) {System.out.print("value="+v+":");}//使用Iterator迭代器迭代輸出System.out.println("\n"+"使用Iterator迭代器進行迭代輸出:");Iterator<Entry<Integer, String>> iter=map.entrySet().iterator();while(iter.hasNext()) {Entry<Integer, String> entry=iter.next();System.out.print("Key="+entry.getKey()+":");System.out.print("value="+entry.getValue()+"--");}}}

    運行結果

    通過Map中的keySet()方法遍歷獲得key值進行輸出: Key=null:value=C--Key=1:value=A--Key=2:value=--Key=3:value=E--Key=4:value=D--Key=8:value=D-- 通過Map中的keySet()方法與valueSet()方法遍歷獲得key值和value值進行輸出 Key=null:Key=1:Key=2:Key=3:Key=4:Key=8:value=C:value=A:value=:value=E:value=D:value=D: 使用Iterator迭代器進行迭代輸出: Key=null:value=C--Key=1:value=A--Key=2:value=--Key=3:value=E--Key=4:value=D--Key=8:value=D--

    注意:hashMap的遍歷輸出結果應該是無序的,我這里不知道為啥,一直是有序的…

    下段代碼來自:https://www.cnblogs.com/biehongli/p/6443701.html 做了一點點小改動

    package Map_demo;import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set;public class HashMap_demo {public static void main(String[] args) {// 1:key,value都是object類型的// 2:key必須是唯一的,不唯一,那么后面的value會把前面的value覆蓋// 3:對于HashMap,key可以為空// 4:value可以為空,也可以為空// 5:HashTable的key和value不能為空// 6:properties的key和value必須為String類型的Map<String, String> map = new HashMap<>();map.put("null", "this is null 1");map.put("null", "this is null 2");map.put(null, "NO NUll");System.out.println(map.size());System.out.println(map.get("null"));System.out.println(map.get(null));System.out.println("=============================");// 循環顯示map類型的key以及對應的value// 三個集合,key的集合,value的集合,鍵值對的集合Set<String> keys = map.keySet();for (String s : keys) {System.out.println(s);}System.out.println("=============================");Collection<String> values = map.values();// 值的集合System.out.println(values);System.out.println("=============================");Set<Map.Entry<String, String>> entrys = map.entrySet();// 鍵值對的集合for (Map.Entry<String, String> entry : entrys) {System.out.println(entry.getKey() + " " + entry.getValue());}} }

    輸出結果:

    2 this is null 2 NO NUll ============================= null null ============================= [NO NUll, this is null 2] ============================= null NO NUll null this is null 2

    總結:上段代碼說明了

    TreeMap:https://mp.csdn.net/mdeditor/98071869#

    HashMap與TreeMap的區別

    a.)Map是接口,HashMap與TreeMap是類(即HashMap和TreeMap實現了Map的所有的方法)
    b.)HashMap非線程安全,TreeMap線程安全
    c.)HashMap:適用于在Map中插入、刪除和定位元素。
    Treemap:適用于按自然順序或自定義順序遍歷鍵(key)。
    d.)HashMap通常比TreeMap快一點(樹和哈希表的數據結構使然),建議多使用HashMap,在需要排序的Map時候才用TreeMap。
    e.)HashMap的結果是沒有排序的,而TreeMap輸出的結果是排好序的。

    HashMap和HashTable的區別
    HashMap和Hashtable都實現了Map接口,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區別有:線程安全性,同步(synchronization),以及速度。

    HashMap幾乎可以等價于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
    HashMap是非synchronized,而Hashtable是synchronized,這意味著Hashtable是線程安全的,多個線程可以共享一個Hashtable;而如果沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
    另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這并不是一個一定發生的行為,要看JVM。這條同樣也是Enumeration和Iterator的區別。
    由于Hashtable是線程安全的也是synchronized,所以在單線程環境下它比HashMap要慢。如果你不需要同步,只需要單一線程,那么使用HashMap性能要好過Hashtable。
    HashMap不能保證隨著時間的推移Map中的元素次序是不變的。

    作者:小北覓
    鏈接:https://www.jianshu.com/p/ee0de4c99f87

    ps:補充小知識
    線程安全
    在Java里,線程安全一般體現在兩個方面:

    1、多個thread對同一個java實例的訪問(read和modify)不會相互干擾,它主要體現在關鍵字synchronized。如ArrayList和Vector,HashMap和Hashtable
    (后者每個方法前都有synchronized關鍵字)。如果你在interator一個List對象時,其它線程remove一個element,問題就出現了。

    2、每個線程都有自己的字段,而不會在多個線程之間共享。它主要體現在java.lang.ThreadLocal類,而沒有Java關鍵字支持,如像static、transient那樣。

    總結

    以上是生活随笔為你收集整理的Map,HashMap,TreeMap的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。