Map旗下映射類總結

java.util包中,Map旗下的映射類有這六個:HashMap、LinkedHashMap、IdentityHashMap、WeakHashMap、TreeMap、EnumMap,下面一一分析

1.HashMap

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

聲明,繼承了AbstractMap類,實現了Map接口
下面看一組相關聯的成員變量和數據類型:

transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        ...
}

從這裏我們就可以得出HashMap的數據結構:
首先HashMap是基於數組的,即這個table數組,該數組中存儲的每一個元素實質上是一個鏈表,也稱爲一個桶,桶裏是一些前後鏈接的Node
table數組的元素是怎樣組織的?什麼樣的Node會被放到一個桶裏?我們接着看:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //重點看i = (n - 1) & hash,此即爲尋桶算法,hash爲key的哈希值,n爲table的長度即桶的個數,(n - 1) & hash即爲該元素要存入的桶的編號,可以看出具有相同hash值的元素會被存入同一個桶裏,但一個桶裏不止有一種hash
        //若桶裏沒有元素,則直接放入該元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    //若桶中不存在hash和key都與該元素相等的元素,則把該元素掛在最後
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //若在桶中存在hash和key都與該元素相等的元素,則認爲key已存在
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //若key已存在,用新value代替舊value,返回舊value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //找到該元素所在的桶
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //在桶中尋找hash和key與該元素都相等的元素
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

從元素的存取方法中可以看出,table元素(桶)的組織方式是,先算出key的hash值,然後利用(n - 1) & hash這個公式計算出桶的序號,即爲該元素要放入的桶。可以看出相同hash值的元素一定會放入同一個桶中,但一個桶中也會包含不同hash值的元素
元素在放入桶之前會被包裝成Node類型,在桶中以鏈表的形式存在。並且會先遍歷鏈表,如果已經有與該元素hash值和key值都相等的結點,則直接修改該結點的值,並返回原值;若沒有與該元素hash值和key值都相等的結點,則把該Node掛在鏈表最後
接下來看其他方法:

//構造方法
public HashMap(int initialCapacity, float loadFactor);
public HashMap(int initialCapacity);
public HashMap();
public HashMap(Map<? extends K, ? extends V> m);

initialCapacity(初始容量)、loadFactor(裝填因子)都有默認值,也可以人爲指定
初始容量指table數組的容量,即桶的個數
填充比達到裝填因子後會進行擴容,即容量變爲原來的兩倍,每個桶中的鏈表會按奇偶次序分到兩個桶中,減少了鏈表長度,提高了查找效率

static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;

這兩個閾值的含義分別是:如果鏈表長度達到8,就把該鏈表轉換爲紅黑樹;如果紅黑樹結點降低爲6個,就把紅黑樹轉換爲鏈表
HashMap的其他方法:

public int size();

public boolean isEmpty();

public V get(Object key);

public boolean containsKey(Object key);

public V put(K key, V value);

public void putAll(Map<? extends K, ? extends V> m);

public V remove(Object key);

public void clear();

public boolean containsValue(Object value);

//三種視圖,無序
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();

public V getOrDefault(Object key, V defaultValue);

public V putIfAbsent(K key, V value);

//key和value都相等時才刪除
public boolean remove(Object key, Object value);

//key和value都相等時才替換
public boolean replace(K key, V oldValue, V newValue);

public V replace(K key, V value);

//函數式接口編程
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);

public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);

//遍歷無序
public void forEach(BiConsumer<? super K, ? super V> action);

public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);

最後需要注意一點,雖然HashMap的遍歷是無序的,但在jdk8中由於hash算法的改變,若key是由Integer或數字型的String組成,則遍歷次序呈現出一種按key升序排列的形式,但仍然要認爲HashMap的遍歷是無序的

2.LinkedHashMap

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

聲明,繼承了HashMap類,實現了Map接口

static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}

可以看到其數據結構Entry繼承了HashMap的Node,並且新增了before、after兩個指針,正是這兩個指針保存了元素的插入次序
構造方法:

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

可以看到,如不明確指定,accessOrder參數默認爲false
accessOrder參數爲false時,視圖和遍歷方法按元素插入順序排列
accessOrder參數爲true時,視圖和遍歷方法按元素訪問次序排列(在插入順序的基礎上,每訪問過一個元素就將它排到最後)
所以LinkedHashMap的三個視圖

public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();

以及其forEach方法:

public void forEach(BiConsumer<? super K, ? super V> action);

其順序都與accessOrder參數有關
其他方法與HashMap類似,不再贅述

3.IdentityHashMap

public class IdentityHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, java.io.Serializable, Cloneable

聲明,繼承了AbstractMap類,實現了Map接口,其內部結構與HashMap類似
看構造方法:

public IdentityHashMap();
public IdentityHashMap(int expectedMaxSize);
public IdentityHashMap(Map<? extends K, ? extends V> m);

其與HashMap的不同點爲,HashMap不允許出現key相同的情況,但IdentityHashMap卻有可能出現相同的key,我們可以從put方法中看出原因:

public V put(K key, V value) {
        final Object k = maskNull(key);

        retryAfterResize: for (;;) {
            final Object[] tab = table;
            final int len = tab.length;
            int i = hash(k, len);

            for (Object item; (item = tab[i]) != null;
                 i = nextKeyIndex(i, len)) {
                //==運算符比較的是地址,而不是key的實際值
                if (item == k) {
                    @SuppressWarnings("unchecked")
                        V oldValue = (V) tab[i + 1];
                    tab[i + 1] = value;
                    return oldValue;
                }
            }

            final int s = size + 1;
            // Use optimized form of 3 * s.
            // Next capacity is len, 2 * current capacity.
            if (s + (s << 1) > len && resize(len))
                continue retryAfterResize;

            modCount++;
            tab[i] = k;
            tab[i + 1] = value;
            size = s;
            return null;
        }
}

對比一下HashMap和IdentityHashMap的put方法中判斷key相等的語句:

//HashMap
(k = p.key) == key || (key != null && key.equals(k))
//IdentityHashMap
item == k

可見HashMap當key的equals方法或==運算有一個爲真時,就判定key相等;而IdentityHashMap只有==運算爲真時,才判定key相等,所以會出現key的值相等的情況
其他方法與HashMap類似,不再贅述

4.WeakHashMap

public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>

聲明,繼承了AbstractMap類,實現了Map接口
看構造方法:

public WeakHashMap(int initialCapacity, float loadFactor);
public WeakHashMap(int initialCapacity);
public WeakHashMap();
public WeakHashMap(Map<? extends K, ? extends V> m);

注意這個數據結構:

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

可以看出WeakHashMap中的鍵值對Entry繼承了WeakReference,即弱引用,所以可能隨時被GC回收,這就是它與HashMap的不同點。這種特性可用於需要緩存的場景
看這個成員變量:

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

被回收的引用保存在這個隊列中,以備更新映射
其他方法與HashMap類似,不再贅述

5.TreeMap

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable

聲明,繼承了AbstractMap類,實現了NavigableMap接口
TreeMap是有序的映射,用紅黑樹進行排序,且有一系列的導航方法

private final Comparator<? super K> comparator;

根據比較器來進行key的排序
看構造方法:

public TreeMap();//無構造器,按key的自然序升序排列
public TreeMap(Comparator<? super K> comparator);
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);

看其他方法:

public int size();

public boolean containsKey(Object key);

public boolean containsValue(Object value);

public V get(Object key);

//返回當前的比較器
public Comparator<? super K> comparator();

public K firstKey();

public K lastKey();

public void putAll(Map<? extends K, ? extends V> map);

public V put(K key, V value);

public V remove(Object key);

public void clear();

public Map.Entry<K,V> firstEntry();

public Map.Entry<K,V> lastEntry();

public Map.Entry<K,V> pollFirstEntry();

public Map.Entry<K,V> pollLastEntry();

public Map.Entry<K,V> lowerEntry(K key);

public K lowerKey(K key);

public Map.Entry<K,V> floorEntry(K key);

public K floorKey(K key);

public Map.Entry<K,V> ceilingEntry(K key);

public K ceilingKey(K key);

public Map.Entry<K,V> higherEntry(K key);

public K higherKey(K key);

public Set<K> keySet() {return navigableKeySet();}
public NavigableSet<K> navigableKeySet();

public NavigableSet<K> descendingKeySet();

public Collection<V> values();

public Set<Map.Entry<K,V>> entrySet();

public NavigableMap<K, V> descendingMap();

public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);

public NavigableMap<K,V> headMap(K toKey, boolean inclusive);

public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

public SortedMap<K,V> subMap(K fromKey, K toKey);

public SortedMap<K,V> headMap(K toKey);

public SortedMap<K,V> tailMap(K fromKey);

public boolean replace(K key, V oldValue, V newValue);

public V replace(K key, V value);

public void forEach(BiConsumer<? super K, ? super V> action);

public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);

6.EnumMap

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable

聲明,繼承了AbstractMap類

private final Class<K> keyType;

keyType指key所屬的枚舉類型
看put方法:

public V put(K key, V value) {
        typeCheck(key);

        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
}

可以看出value存在vals數組中,其順序與枚舉key的順序是一致的,所以用key.ordinal()得到key的索引,也就得到了對應的value的索引。故其查找速度要比HashMap快
看構造方法:

public EnumMap(Class<K> keyType);
public EnumMap(EnumMap<K, ? extends V> m);
public EnumMap(Map<K, ? extends V> m);

再看其他方法:

public int size();

public boolean containsValue(Object value);

public boolean containsKey(Object key);

public V get(Object key);

public V put(K key, V value);

public V remove(Object key);

public void putAll(Map<? extends K, ? extends V> m);

public void clear();

public Set<K> keySet();

public Collection<V> values();

public Set<Map.Entry<K,V>> entrySet();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章