java之Map源碼淺析

來源:互聯網
上載者:User

標籤:java   map   源碼   

Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見:


JDK中有不同的的map實現,分別適用於不同的應用情境,如安全執行緒的hashTable和非安全執行緒的hashMap.

如是JDK中map介面的子類UML類圖,其中有個特例Dictionary已經不建議使用:


Map介面中的方法我們需要關注的就是get、put 和迭代器相關的方法如entrySet()、keySet()、values()方法。

Entry

在開始分析map之前,首先瞭解map中元素的儲存,我們知道map可以認為是鍵值對的集合,java中map使用Entry儲存鍵值對,這是一個介面,其定義如下,簡單明了,介面方法主要是對鍵和值進行操作。

interface Entry<K,V> {       K getKey();     V getValue();    V setValue(V value);     boolean equals(Object o);     int hashCode();    }

AbstractMap

Map介面的抽象實現,見以下樣本實現代碼:

Map<String,String> a = /**        *        *抽象map實現示意,根據文檔說,和list介面及其類似。        *        *map分為可變和不可變兩種,不可變只需實現 entrySet方法即可,且返回的 set的迭代器不能支援修改操作。        *        *可變map,需要實現put方法,然後 entrySet的迭代器也需要支援修改操作        *        *        *AbstractMap 裡面實現了map的梗概,但是其效率難說,比如其get方法中時採用方法entrySet實現的。        *        *通常子類用更有效率的方法覆蓋之。如hashMap中覆蓋了keySet 、values 、get方法等        */       new AbstractMap<String,String>(){            /*            * 返回map中的元素集合,返回的集合通常繼承AbstractSet 即可。            */           @Override           public Set<Map.Entry<String, String>> entrySet() {              return new AbstractSet<Map.Entry<String,String>>() {                   @Override                  public Iterator<java.util.Map.Entry<String, String>> iterator() {                     return null;                  }                   @Override                  public int size() {                     return 0;                  }              };           }                     /*            * 預設實現拋出異常,可變map需要實現此方法            */           @Override           public String put(String key, String value) {                           return null;           }                 };

HashMap

hashMap繼承abstractMap,是相當常用的資料結構,採用hash散列的思想,可以在O(1)的時間複雜度插入入和擷取資料。其基本實現可以分析上個小節中的抽象方法,文章

淺析HashMap的實現和效能分析 已經對hashMap的實現、put和get操作進行了較詳細的說明。這裡不再贅述,關鍵看他的迭代器實現,這裡只分析下entrySet()方法,而keySet()和values()方法實現與之一脈相承。

關於迭代器,見下面摘出的部分源碼和相關注釋:

/**         * 返回map中所有的索引值對集合,用於遍曆         */        public Set<Map.Entry<K,V>> entrySet() {       return entrySet0();        }         /**         * 延遲初始化,只有使用的時候才構建。         *         * values()和 keySet()方法也使用了類似的機制         */        private Set<Map.Entry<K,V>> entrySet0() {            Set<Map.Entry<K,V>> es = entrySet;            return es != null ? es : (entrySet = new EntrySet());        }                       /**         * 真正的 enterySet,是一個內部類,其關鍵實現是迭代器實現。         *         * values()和 keySet()方法也對應了相應的內部類。         * 對應的自己的迭代器實現。關鍵在於這個迭代器         *         */        private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {            public Iterator<Map.Entry<K,V>> iterator() {                return newEntryIterator();            }            public boolean contains(Object o) {                if (!(o instanceof Map.Entry))                    return false;                Map.Entry<K,V> e = (Map.Entry<K,V>) o;                Entry<K,V> candidate = getEntry(e.getKey());                return candidate != null && candidate.equals(e);            }            public boolean remove(Object o) {                return removeMapping(o) != null;            }            public int size() {                return size;            }            public void clear() {                HashMap.this.clear();            }        }                      /**         * entrySet迭代器,繼承HashIterator,實現next方法。         * values()和 keySet()方法,也是繼承HashIterator,只是實現next 的方法不同,         *         * 可以對比下。         *         * 關鍵在於HashIterator         *         *         */        private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {            public Map.Entry<K,V> next() {                return nextEntry();            }        }               /**         *         *keySet()對應的迭代器         */        private final class KeyIterator extends HashIterator<K> {            public K next() {                return nextEntry().getKey();            }        }                      /**         *         * hashmap entrySet() keySet() values()的通用迭代器         */        private abstract class HashIterator<E> implements Iterator<E> {            Entry<K,V> next;   // next entry to return            int expectedModCount; // For fast-fail            int index;     // current slot            Entry<K,V> current;   // current entry             HashIterator() {                expectedModCount = modCount;                if (size > 0) { // advance to first entry                    Entry[] t = table;                    //構造時候,在數組中尋找第一個不為null的數組元素,即Entry鏈表,關於hashmap的實現請看                    //本人前面的博文                    while (index < t.length && (next = t[index++]) == null)                        ;                }            }             public final boolean hasNext() {                return next != null;            }             /**             * 關鍵實現,很容易看懂,尋找next的時候,和構造迭代器的時候一樣             */            final Entry<K,V> nextEntry() {                if (modCount != expectedModCount)                    throw new ConcurrentModificationException();                Entry<K,V> e = next;                if (e == null)                    throw new NoSuchElementException();                 if ((next = e.next) == null) {                    Entry[] t = table;                    while (index < t.length && (next = t[index++]) == null)                        ;                }           current = e;                return e;            }             public void remove() {                if (current == null)                    throw new IllegalStateException();                if (modCount != expectedModCount)                    throw new ConcurrentModificationException();                Object k = current.key;                current = null;                HashMap.this.removeEntryForKey(k);                expectedModCount = modCount;            }         }

HashTable

實現和hashMap基本一致,只是在方法上加上了同步操作。多線程環境可以使用它。不過現在有ConcurrentHashMap了,在高並發的時候,可以用它替換hashtable.

LinkedHashMap

hashMap可能在某些情境下不符合要求,因為放入到其中的元素是無序的。而LinkedHashMap則在一定程度上解決這個問題。

其在實現上繼承了HashMap,在儲存上擴充haspMap.enteySet,加入了before、after欄位,把hashMap的元素用雙向鏈表串連了起來。這個雙向鏈表決定了它的遍曆順序。其順序通常是插入map中的順序,但是它有一個欄位accessOrder當為true時,遍曆順序將是LRU的效果。

研究它的有序性,我們可以從put方法、get方法和遍曆的方法入手,首先看get方法:

/**         * 直接調用父類的getEntry方法。關鍵在於         *  e.recordAccess(this) 這句代碼         */        public V get(Object key) {            Entry<K,V> e = (Entry<K,V>)getEntry(key);            if (e == null)                return null;            e.recordAccess(this);            return e.value;        }               /**         * Entry.recordAccess 方法         *         * 如果是訪問順序(accessOrder=true),那麼就把它放到頭結點的下一個位置         * 否則什麼也不做,         * 這樣就可以根據初始 accessOrder 屬性,來決定遍曆的順序。         */        void recordAccess(HashMap<K,V> m) {            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;            if (lm.accessOrder) {                lm.modCount++;                remove();                addBefore(lm.header);            }        }


Put方法:

/**         * put方法調用此方法,覆蓋了父類中的實現,         */        void addEntry(int hash,K key, V value, int bucketIndex) {            createEntry(hash, key, value, bucketIndex);             // Remove eldest entry if instructed, else grow capacity if appropriate            Entry<K,V> eldest = header.after;            //回調。如果有必要移除在老的元素,最新的元素在鏈表尾部。            if (removeEldestEntry(eldest)) {                removeEntryForKey(eldest.key);            } else {                if (size >= threshold)                    resize(2 * table.length);            }        }         /**         *         */        void createEntry(int hash,K key, V value, int bucketIndex) {            HashMap.Entry<K,V> old = table[bucketIndex];       Entry<K,V> e = new Entry<K,V>(hash, key, value, old);            table[bucketIndex] = e;            //本質是插入雙向鏈表的末尾            e.addBefore(header);            size++;        }                      /**         * 插入到 existingEntry的前面,因為是雙向鏈表。當existingEntry是 header時,         * 相當於插入到鏈表最後。         *         */        private void addBefore(Entry<K,V> existingEntry) {            after  = existingEntry;            before = existingEntry.before;            before.after = this;            after.before = this;        }


遍曆迭代直接使用雙向鏈表進行迭代介面,這裡不贅述,可以看源碼很容易理解。注意的是實現上市覆蓋了父類中相關的產生迭代器的方法。

TreeMap和CurrentHashMap都可以單獨開一篇文章來分析了。這裡簡單說下。TreeMap是基於b樹map,根據key排序。CurrentHashMap是並發包中的一個強大的類,適合多線程高並發時資料讀寫。

 

 

java之Map源碼淺析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.