Java經常使用數據結構之Map-AbstractMap

前言

Map集合是用來存儲<Key, Value>鍵值對數據的,是平常開發中使用最多的數據結構之一。Map集合相對List集合來講結構會稍微複雜一些,因此Map系列會分開寫。本文主要分析AbstractMap。html

類圖

Map類圖

Map接口中定義了各類基本方法,而鍵值對數據實際是保存在Entry中的。AbstractMap類和AbstractList類同樣,都是一種模板類,提供了Map的基本實現。開發人員若是想實現本身的Map,只須要繼承AbstractMap類,實現特定方法便可。

源碼分析

惟一的抽象方法

整個AbstractMap類中只有一個抽象方法:java

public abstract Set<Entry<K,V>> entrySet();
複製代碼

也就是說全部的子類都必須實現entrySet()方法。縱觀AbstractMap中的成員方法內部實現,基本都依賴於entrySet()方法,它返回了Map所保存的鍵值對。api

重要的成員方法

AbstractMap有個默認拋UnsupportedOperationException異常的方法:安全

public V put(K key, V value) {
        throw new UnsupportedOperationException();
    }
複製代碼

整個put方法直接影響了:bash

public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
複製代碼

也就是說Map默認是不支持修改的,子類若是想實現可變的Map,則須要重寫put方法。
有put就應該有remove,來看看remove方法:微信

public V remove(Object key) {
        // 使用到了entrySet()獲取保存的數據
        Iterator<Entry<K,V>> i = entrySet().iterator();
        Entry<K,V> correctEntry = null;
        // 經過迭代器找到要remove的值
        if (key==null) {
            while (correctEntry==null && i.hasNext()) {
                Entry<K,V> e = i.next();
                if (e.getKey()==null)
                    correctEntry = e;
            }
        } else {
            while (correctEntry==null && i.hasNext()) {
                Entry<K,V> e = i.next();
                if (key.equals(e.getKey()))
                    correctEntry = e;
            }
        }

        V oldValue = null;
        if (correctEntry !=null) {
            oldValue = correctEntry.getValue();
            i.remove(); // 調用迭代器的remove方法
        }
        return oldValue;
    }
複製代碼

Map的remove方法使用到了entrySet()所返回Set的迭代器的remove方法。數據結構

因此若是你想實現本身的Map結構:
1.當要實現一個不可變的Map時,須要繼承AbstractMap,而後實現entrySet() 方法,這個方法返回一個保存全部key-value映射的Set。一般這個Set不支持add()和remove() 方法,Set對應的迭代器也不支持remove()方法。
2. 當要實現一個可變的 Map時,須要在上述操做外,重寫put()方法,並且entrySet()返回的Set 的迭代器須要實現remove()方法。oracle

重要的成員變量

AbstractMap只有兩個成員變量:源碼分析

transient Set<K>        keySet; // 不可序列化
    transient Collection<V> values; // 不可序列化
複製代碼

注意:從jdk1.8開始,這兩個變量再也不使用volatile修飾,由於調用這兩個變量的方法不是同步的,增長volatile也不能保證線程安全。(本文用的是jdk11)this

這裏看一下怎麼獲取keySet:

public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            // 自定義一個Set而且實現迭代器
            ks = new AbstractSet<K>() {
                public Iterator<K> iterator() {
                    return new Iterator<K>() {
                        // 使用Entry的Set集合的迭代器
                        private Iterator<Entry<K,V>> i = entrySet().iterator();

                        public boolean hasNext() {
                            return i.hasNext();
                        }

                        public K next() {
                            return i.next().getKey();
                        }

                        public void remove() {
                            i.remove();
                        }
                    };
                }

                public int size() {
                    return AbstractMap.this.size();
                }

                public boolean isEmpty() {
                    return AbstractMap.this.isEmpty();
                }

                public void clear() {
                    AbstractMap.this.clear();
                }

                public boolean contains(Object k) {
                    return AbstractMap.this.containsKey(k);
                }
            };
            keySet = ks;
        }
        return ks;
    }
複製代碼

這裏寫的很巧妙,沒用採用遍歷Entry的方式,而是實現了一個自定義Set集合。這個集合再重寫iterator方法,直接調用Entry集合的迭代器。values也作了一樣的處理。

兩個內部類

AbstractMap有兩個內部類SimpleEntry<K,V>SimpleImmutableEntry<K,V>,它們都實現了Entry<K,V>Serializable

SimpleEntry:表示值可變的鍵值對。

private final K key; // 不可變
    private V value;
複製代碼

提供了相應的setValue方法:

public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue; // 注意返回的是舊值
        }
複製代碼

來看看equals方法:

public boolean equals(Object o) {
            if (!(o instanceof Map.Entry)) // 判斷類型
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o; // 由於泛型編譯時會被擦除,因此使用?號
            return eq(key, e.getKey()) && eq(value, e.getValue());
        }
        
    private static boolean eq(Object o1, Object o2) {
        // 由於實際中o1極可能是String類型,因此這裏使用了equals,而不是==
        return o1 == null ? o2 == null : o1.equals(o2);
    }
複製代碼

SimpleImmutableEntry:表示不可變的鍵值對。

private final K key; // 不可變
    private final V value; // 不可變
複製代碼

它的setValue方法直接拋出異常:

public V setValue(V value) {
            throw new UnsupportedOperationException();
        }
複製代碼

總結

  1. AbstractMap的核心方法是entrySet(),子類必須實現;
  2. Entry是存儲鍵值對的數據結構,子類根據Map的特色,構造不一樣的Entry;

參考資料

  1. Java集合中的AbstractMap抽象類
    www.cnblogs.com/yulinfeng/p…
  2. Java 集合深刻理解(15):AbstractMap
    blog.csdn.net/u011240877/…
  3. jdk8 api文檔
    docs.oracle.com/javase/8/do…

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索