java集合梳理

平時在寫代碼時,經常用到java的集合,使用最多的就是ArrayList與HashMap等,對其它的接口就不甚瞭解。最近抽出時間對集合整理一下。
collection是集合最頂層接口,位於java.utils包中,接口繼承了Iterable接口,因此集合實現迭代功能。

public interface Collection<E> extends Iterable<E> {

繼承Collection接口的接口包括Set、List、Queue。這些接口及其實現類關係圖:

此圖是引用別人的,相互學習,理解

請先忽略圖中的Map接口,稍後理解Map接口。
實現List接口的類包括Vector、ArrayList、LinkedList。因此這三者共同的特徵是存儲的數據有序、可重複。三者之間的區別爲:

  1. ArrayList是基於數組的列表,內部元素可以通過get與set訪問。適合於數據的查詢與遍歷。
  2. LinkedList是雙鏈表結構,在添加或刪除元素方面比ArrayList更適合。
  3. Vector與ArrayList結構類似,但是Vector有一個重要特徵就是線程安全,因此適合併發多線程操作。另外Vector還有一個子類Stack(棧),Stack的特徵是先進後出

Set是最簡單的一種集合,相當於一個可變大小的數組,它的特徵是數據無序、不能重複,這些特徵是因爲它的實現類底層結構是Map,數據存儲在Map的key上。實現Set接口的類包括HashSet、TreeSet、linkedHashSet。內部各自使用的Map結構對應HashMap、TreeMap、LinkedHashMap。

Queue是一種隊列集合,發現圖中沒有實現該接口的類,其實LinkedList是實現了Queue接口:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
public interface Deque<E> extends Queue<E> {

因此LinkedList具有隊列的特徵,即先進先出。另外對於多線程編程模式在很大程序上都依賴於Queue實現的線程安全性的知識會在另一博文中單獨討論。

Map是一個鍵值對形式的集合,它的元素由鍵和值組成,其中鍵(key)是唯一的,值(value)可以重複,鍵值被封裝在Entry對象中。在底層它是基於數組和鏈表的混合數據結構。實現Map接口的類包括HashMap、HashTable、TreeMap、WeakHashMap和IdentityHashMap

  1. HashMap 線程不安全、效率高。通過hashcode、equal來確保鍵的唯一性。HashMap添加元素的原理步驟:
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //第一步,判斷key是否爲null,jdk將key爲null時單獨處理了,其實與非null的key的操作一致
        if (key == null)
            return putForNullKey(value);
        //第二步,獲取key的hash值
        int hash = hash(key);
        //第三步,根據hash值和數組的長度計算需要將元素放置的下標
        int i = indexFor(hash, table.length);
        //第四步,遍歷該下標中的Entry鏈表,判斷要添加元素的key與現有元素key是否一致,根據hash值與equals方法判斷;若存在,替換value,結束;否則直到next爲null結束。
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //第五步,相同key不存在,添加鍵值對元素。
        addEntry(hash, key, value, i);
        return null;
    }
 void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        //第六步,創建Entry,bucketIndex爲下標
        createEntry(hash, key, value, bucketIndex);
    }
void createEntry(int hash, K key, V value, int bucketIndex) {
        //第七步,獲取數組當前下標的Entry
        Entry<K,V> e = table[bucketIndex];
        //第八步,數組當前下標指向新添加的Entry,新添加的entry中next指向原始數組下標對應的Entry
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

可知,HashMap中數據是無序的。HashMap還有一個子類LinkedHashMap,LinkedHashMap是在HashMap的基礎上維護了一個雙向循環鏈表,鏈表定義迭代順序,該順序就是數據存入的順序,因此LinkedHashMap與HashMap不同的地方就是它是有序的。由於LinkedHashMap額外維護鏈表,因此效率不如HashMap。

2.HashTable 是線程安全的, 效率低。HashTable不僅實現了Map接口,還繼承了Dictionary類,

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

因此,HashTable在遍歷數據時使用的是枚舉,不能使用迭代器,而目前迭代器已經逐漸取代枚舉,另外HashTable不允許null,hashMap允許null,在獲取元素時,HashTable也是線程安全的;等等其他原因,建議HashMap替換HashTable,若爲了達到線程安全,可以使用concurrentMap,或者collections中的synchronizedMap()方法包裹HashMap。

3.TreeMap是基於紅黑二叉樹實現的。因此TreeMap是有序的,而且是唯一帶有subMap()方法的Map,它可以返回一個子樹。關於紅黑二叉樹實現原理請參閱另外兩篇博文:
java源碼分析之TreeMap基礎篇
通過分析 JDK 源代碼研究 TreeMap 紅黑樹算法實現

4.WeakHashMap 短時間內緩存等用途。

5.IdentityHashMap,在判斷key是否相等時,用的是“==”,因此允許內容相同的多個對象的key存在

結束!!!

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