【Java 】 集合結構:HashMap、ArrayList等詳解

鍵值對Map家族說明

Java爲數據結構中的鍵值對定義了一個接口java.util.Map,此接口主要有四個常用的實現類,分別是HashMap、Hashtable、LinkedHashMap和TreeMap,類繼承關係如下圖所示:
Map家族類圖
下面針對各個實現類的特點做一些說明:

  1. HashMap:它根據鍵的hashCode值存儲數據,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashMap最多隻允許一條記錄的鍵爲null,允許多條記錄的值爲null。HashMap非線程安全,即任一時刻可以有多個線程同時寫HashMap,可能會導致數據的不一致。如果需要滿足線程安全,可以用 Collections的synchronizedMap方法使HashMap具有線程安全的能力,或者使用ConcurrentHashMap。
  2. Hashtable:Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它承自Dictionary類,並且是線程安全的,任一時間只有一個線程能寫Hashtable,併發性不如ConcurrentHashMap,因爲ConcurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換。
  3. LinkedHashMap:LinkedHashMap是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時帶參數,按照訪問次序排序。
  4. TreeMap:TreeMap實現SortedMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。如果使用排序的映射,建議使用TreeMap。在使用TreeMap時,key必須實現Comparable接口或者在構造TreeMap傳入自定義的Comparator,否則會在運行時拋出java.lang.ClassCastException類型的異常。

對於上述四種Map類型的類,要求映射中的key是不可變對象。不可變對象是該對象在創建後它的哈希值不會被改變。如果對象的哈希值發生變化,Map對象很可能就定位不到映射的位置了。

HashMap存儲結構詳解

首先來看JDK8中HashMap存儲結構示例代碼

/** Jdk8 HashMap 分解詳解 */
public class HashMapAnalysis<K,V> {

    /** Node實現了Map.Entry<K,V>用於數據存儲鍵值對,如果K的hashCode相同,存儲在Node實例中鏈表結構next當中 */
    transient Node<K,V>[] table;

    /** 當前Map的鍵值對的數量 */
    transient int size;

    /** Map需要擴容的負載因子,默認0.75 */
    final float loadFactor = 0.75f ;

    /** 下一個元素大小,如果達到擴容數字(容量*加載因子),需要resize(雙倍擴容) */
    int threshold;

    /** HashMap中存儲對象Node對象結構 */
    private class Node<K, V> implements Map.Entry<K,V>{
        /** 當前Key的hashCode值 */
        final int hash;
        /** 當前Entry中Key對象 */
        final K key;
        /** 當前Entry中Value對象 */
        V value;
        /** 當前Entry中Key的hashCode一樣Entry的下一個Entry(鏈表存儲) */
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;  this.key = key;  this.value = value;  this.next = next;
        }
    }
}

看屬性元素的說明,就能知道意思了,也可以使用下圖來理解:
HasHMap存儲模型

HashMap非線程安全導致死循環問題說明

在多線程工作環境下,HashMap容量達到臨界值的put操作後,其他線程get操作容易導致死循環。是因爲HashMap容量達到臨界值會進行2倍擴容,擴容完成之後會進行數據轉移的工作在transfer(JDK1.8中已沒有此方法)方法中,單線程時相同hashCode的Entry鏈表數據轉移完成後元素會倒序,如果此時發生多線程操作,就容易造成死循環,死循環原理詳解本文不詳細說明(內容較多可另起文章說明)。ConcurrentHashMap將原本HashMap的單個數組存儲形式,分解成多個段(Segment)的形態,每個段裏面的結構和以前HashMap結構類似,這樣使用分段鎖,來減少鎖的範圍,提高併發效率。

HashMap常見面試題

HashTable, HashMap,TreeMap區別?

  • HashTable線程同步,HashMap非線程同步
  • HashTable不允許<鍵,值>有空值,HashMap允許<鍵,值>有空值
  • HashTable使用Enumeration,HashMap使用Iterator
  • HashTable中hash數組的默認大小是11,增加方式的old*2+1,HashMap中hash數組的默認大小是16,增長方式一定是2的指數倍。
  • TreeMap能夠把它保存的記錄根據鍵排序,默認是按升序排序

HashMap是不是有序的連環炮式發問

【你肯定回答說,不是有序的】。那面試官就會繼續問你,有沒有有順序的Map實現類? 【你說有TreeMap和LinkedHashMap】。 那麼面試官接下來就可能會問你,TreeMap和LinkedHashMap是如何保證它的順序的?【LinkedHashMap根據put的順序保證有序,TreeMap根據Key元素的Comparable接口實現方法保證有序】。 如果你依然回答上來了,那麼面試官還會繼續問你,你覺得它們兩個哪個的有序實現比較好? 如果你依然可以回答的話,那麼面試官會繼續問你,你覺得還有沒有比它更好或者更高效的實現方式? 如果你還能說出來的話,那麼就你所說的實現方式肯定依然可以問你很多問題。 以上就是一個面試官一步一步提問的例子。所以,如果你瞭解的不多,千萬不要敷衍,因爲可能下一個問題你就暴露了,還不如直接說不會,把這個問題結束掉,趕緊切換到你熟悉的領域。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章