我從未見過如此精闢的解說方式,雙列集合框架 Map,看一遍就夠了

1.經常使用的實現類結構

1、HashMap

實現了Map、Cloneable、Serializable接口,繼承了AbstractMap類面試

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable     
    /**
     * Map接口: 實現鍵值對,Map接口規定了一個key對應一個value
     *          HashMap使用該接口用來替換Dictionary類
     *
     * AbstractMap類: 繼承Map的抽象類,減小Map操做的實現
     *
     * Cloneable接口: 能夠顯示的調用Object.clone()方法,合法的對該類
     *                實例進行字段複製
     *
     * Serializable接口: 實現該接口後,該類能夠被序列化和反序列化
     */

1.HashMap是否線程安全?

HashMap是線程不安全的,在併發的環境下可使用ConcurrentHashMap。數組

2.HashMap的內部實現

內部實現:在JDK1.8以前是數組+鏈表,JDK1.8以後是數組+鏈表+紅黑樹
加入紅黑樹的緣由:JDK1.8以前HashMap使用的是數組加鏈表,因爲哈希函數不能百分百的讓元素均勻的分佈,就會形成有大量的元素存入同一個index(桶)下,這樣index就造成了一條很長的鏈表,由此元素的遍歷的時間複雜度爲O(n),失去了HashMap的優點,加入了紅黑樹,查找的時間複雜度爲O(log n),實現了優化
數組的特色:查找快,時間複雜度是O(1),增刪慢,時間複雜度是O(n)
鏈表的特色:查找慢,時間複雜度是O(n),增刪快,時間複雜度是O(1)
紅黑樹的特色:在遍歷鏈表的基礎上,紅黑樹查找的時間複雜度是O(log n)
紅黑樹的查詢效率遠大於鏈表,可是插入/刪除要比鏈表慢安全

3.HashMap的主要參數

1.默認初始容量:16,必須是2的整數次方併發

2.默認加載因子:0.75函數

3.閾值:容量*加載因子性能

4.樹形閾值:默認是8,當bucket中的鏈表長度大於8時,則進行鏈表樹化優化

5.非樹形閾值:默認是6,當進行擴容時,當進行擴容(resize())時(這時候的HashMap的數據存儲位置會從新計算),在計算完後,原有的紅黑樹內的數量<6時,則由紅黑樹轉換爲鏈表spa

6.樹形最小容量:桶多是樹的哈希表的最小容量,至少是TREEIFY_THRESHOLD 的 4 倍,這樣能避免擴容時的衝突線程

//鏈表轉紅黑樹的閾值
static final int TREEIFY_THRESHOLD = 8;
//紅黑樹轉鏈表的閾值
static final int UNTREEIFY_THRESHOLD = 6;
/**
*最小樹形化容量閾值:即 當哈希表中的容量 > 該值時,才容許樹形化鏈表 (即 將鏈表 轉換成紅黑樹)
*不然,若桶內元素太多時,則直接擴容,而不是樹形化
*爲了不進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD
**/
static final int MIN_TREEIFY_CAPACITY = 64;

只有在數組的長度大於64時,且鏈表的長度>8時,才能樹形化鏈表
鏈表的長度大於8時會調用treeifyBin方法轉換爲紅黑樹,可是treeifyBin方法內部有一個判斷,當只有數組的長度>64的時候,才能將鏈表樹形化,不然只進行resize擴容
由於鏈表過長而數組太短,會常常發生hash碰撞,這時進行樹形化的做用不大,由於鏈表過長的緣由就是數組太短。樹形化以前要檢查數組的長度,<64進行擴容,而不是進行樹形化
鏈表的長度>8,但數組的長度<64時,不會進行樹形化,而是進行resize後rehash從新排序設計

4.HashMap的經常使用方法

添加:V put(K key,V value) -->添加元素(也能夠實現修改)

刪除:void clear() -->清空全部鍵值對元素

​ V remove(Object key) -->根據鍵刪除對應的值,並把值返回

判斷:containsKey(Object key) -->是否包含指定的鍵

​ containsValue(Object value)–>是否包含指定的值

遍歷:Set<Map.Entry<K,V>> entrySet()–>獲取鍵值對

​ V get(Object key) -->根據鍵獲取值

​ Collection value()–>獲取值的集合

獲取:Set setKey()–>獲取鍵的集合

​ int size()–>獲取集合元素的個數

基本方法的使用

HashMap<Integer,String> map=new HashMap<>();
        //添加元素
        map.put(1, "a");
        map.put(2, "b");
        map.put(3, "c");
        //鍵不可重複,值被覆蓋
        map.put(3, "C");
        
        //經過鍵刪除整個鍵值對
        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
        //獲取集合元素個數
        System.out.println(map.size());//3
        //經過鍵獲取值
        System.out.println(map.get(3));//C
        //獲取全部值構成的集合
        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);
  }
  //經過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
    }
  }
  //經過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.關於hash衝突問題

1.緣由:

​ 當對某個元素進行哈希運算後,獲得一個存儲地址,在進行插入的時候,發現已經被其餘元素佔用了。這就是所謂的哈希衝突,也叫哈希碰撞。

哈希函數的設計相當重要,好的哈希函數會盡可能的確保計算簡單和散列地址分佈均勻,可是,數組是一塊連續的固定的長度的內存空間,再好的哈希函數也不能保證獲得的存儲地址絕對不發生衝突。
2.解決hash衝突的方法:

開放地址法:發生衝突,繼續尋找下一塊未被佔用的存儲地址。
再散列函數法:當發生衝突,使用第二個、第三個、哈希函數計算地址,直到無衝突。
鏈地址法:將全部關鍵字的同義詞的記錄存儲在一個單鏈表中,咱們稱這種表爲同義詞子表。
HashMap採用的是鏈地址法,也就是數組+鏈表的方式

HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,若是定位到的數組位置不含鏈表(當前的entry的next指向null),那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,不然新增,而後經過key對象的equals方法逐一比對查找。因此!對於性能考慮,HashMap中的鏈表出現越少,性能纔會越好。

6.JDK1.8以前HashMap存在死循環的問題

緣由:因爲數組擴容後,同一索引位置的節點順序會反掉

7.HashMap和Hashtable的區別

8.重寫equals()方法後,是否必定要重寫hashCode()方法?爲何?

重寫equals()方法,須要重寫hashCode()方法。

hashCode規定:兩個對象相等(即equals()返回true),hashCode必定相同;兩個對象hashCode相同,兩個對象不必定相等;

重寫equals,而不重寫hashCode方法,默認調用的是Object類的hashCode()方法,會致使兩個對象的equals相同但hashCode不一樣。簡單的來講,重寫equals方法後,重寫hashCode方法就是爲了確保比較的兩個對象是同一對象。

2、LinkedHashMap

LinkedHashMap的底層結構和HashMap是相同的,由於LinkedHashMap繼承了HashMap,

區別在於:LinkedHashMap內部提供了Entry,替換了HashMap中的Node。

LinkedHashMap:保證在遍歷map元素時,能夠照添加的順序實現遍歷

緣由:在原來的HashMap底層結構的基礎上,增長了一對指針,指向前一個和後一個元素

對於頻繁的遍歷操做,LinkedHashMap的效率要高於HashMap

3、TreeMap

保證照key-value對進行排序,實現排序遍歷,此時考慮key的天然排序或者定製排序,底層使用的是紅黑樹

元素根據鍵排序,元素具備惟一性

1)天然排序

讓元素所在的類繼承天然排序Comparable

2)比較器排序

讓集合的構造方法接收一個比較器接口(Comparator的實現對象)

4、Hashtable

做爲古老的實現類;線程安全的,效率低;不能存儲null的key和value

最後

你們看完有什麼不懂的能夠在下方留言討論,也能夠關注我私信問我,我看到後都會回答的。也歡迎你們關注個人公衆號:前程有光,金三銀四跳槽面試季,整理了1000多道將近500多頁pdf文檔的Java面試題資料,文章都會在裏面更新,整理的資料也會放在裏面。謝謝你的觀看,以爲文章對你有幫助的話記得關注我點個贊支持一下!

相關文章
相關標籤/搜索