1. 程式人生 > >java之Map原始碼淺析

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是併發包中的一個強大的類,適合多執行緒高併發時資料讀寫。

相關推薦

javaMap原始碼淺析

Map是鍵值對,也是常用的資料結構。Map介面定義了map的基本行為,包括最核心的get和put操作,此介面的定義的方法見下圖: JDK中有不同的的map實現,分別適用於不同的應用場景,如執行緒安全的hashTable和非執行緒安全的hashMap. 如下圖是JDK中m

javaSet原始碼淺析

Set的介面和實現類是最簡單的,說它簡單原因是因為它的實現都是基於實際的map實現的。如 hashSet 基於hashMap,TreeSet 基於TreeMap,CopyOnWriteArraySet 基於 CopyOnWriteArrayList 。 故對其實現簡要分析。

javalist原始碼淺析

三大資料結構連結串列、樹和圖,順序表作為其中的一種,可以說是平時程式設計中最長使用到的。List介面是順序表在java中的實現,它有很多子介面和實現類,平時的程式設計中使用起來非常方便。但是更進一步,我們有必要對其實現和原理進行理解,並和資料結構中所學比較,並應用於平時的程

第一章 JAVA集合HashMap原始碼淺析

              屌絲程式設計師的奮鬥之路現在開始                java集合這一塊無論在面試或在寫程式碼中,我們都會接觸到,所以java集合是特別重要的,其中HashMap更是被我們經常用到。  一.概括                HashM

Javamap使用方法

str basic 添加元素 修改 添加 col pack mov 遍歷 1 package basic; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 //map使用方法 7 p

javaMap集合遍歷幾種方法

package cn.com.javatest.collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * java之Map集合遍歷幾種方法 * * @author:

JavaMap實現升級版

package learn.java.cn.collection; import java.util.LinkedList; /* * 思路:陣列中放list,list中放DefMap,即陣列加連結串列; * 存放時,key的hashcode值 對陣列長度求餘後作為索引值 存放到陣列中,

JavaMap實現初級版

package learn.java.cn.collection; public class MyMap { int size=0; DefMap [] defMap=new DefMap[999]; public void put(Object key,Object value) { /

Java基礎—ArrayList原始碼淺析

注:以下原始碼均為JDK8的原始碼 一、 核心屬性   基本屬性如下:      核心的屬性其實是紅框中的兩個:      //從註釋也容易看出,一個是集合元素,一個是集合長度(注意是邏輯長度,即元素的個數,而非陣列長度)   其中:transient指明序列化時請忽略。  二、構造

java 通訊錄原始碼

1️⃣聯絡人類 package com.kll.LinkMan; public class LinkMan { //聯絡人:姓名 年齡 性別 地址 電話 private String name = null; private int age = 0

Java中HashMap原始碼淺析

在Java編碼中可以說HashMap的使用是可以說是無處不在的,對於HashMap的實現原理沒有去過多深入學習,一直停留在使用階段。現在想來還是要一探HashMap的實現原理,不要一味的只是停留在使用階段。而且HashMap的原理在很多面試中都會問到哦,所以弄清

Java 集合框架 原始碼淺析 與理解

最近在研究java原始碼,就是看一看別人寫好的東西,也不算是研究。知根知底的對以後的學習會有很大的幫助,我先去了解一下java集合框架,從總體上對這個組織和操作資料的資料結構有個淺顯得的瞭解。 從網上看了很多資料,發現這一張圖總結的還算不錯就引用過來了。但是最

[JDK1.6] JAVA集合 Hashtable 原始碼淺析

文章目錄 (一) 簡介: Hashtable 體系結構: Hashtable 欄位屬性: Hashtabl 儲存 (鍵-值) 的節點 Entry: 構造方法: 儲存 鍵-值 put(K, V) 擴容 rehash: 獲取資料

重拾JavaLinkedList原始碼閱讀

     上文我們檢視ArrayList的原始碼(重拾Java之ArrayList原始碼閱讀),接著我們來瞅瞅LinkedList有什麼神奇之處。ArrayList的資料儲存方式是陣列,LinkedList裡面儲存資料的方式是連結串列,什麼是連結串列了?你可以將其理解為一列火

常用集合LinkedHashMap原始碼淺析

眾所周知 HashMap 是一個無序的 Map,因為每次根據 key 的 hashcode對映到Entry陣列上,所以遍歷出來的順序並不是寫入的順序。 因此 JDK 推出一個基於 HashMap 但具有順序的 LinkedHashMap 來解決有排序需求的場景

JavaLinkedList原始碼解讀(JDK 1.8)

java.util.LinkedList     雙向連結串列實現的List。    基於JDK 1.8。    沒有使用標準的註釋,並適當調整了程式碼的縮排以方便介紹。    裡面很多方法的實現是一樣的,不過可以讓外界感覺其提供了更多的行為。    需要花比Array

JavaHashMap原始碼解讀

HashMap一直是陣列加連結串列的資料結構,在陣列的某個下標位置,有多次碰撞,則使用連結串列資料結果儲存。在jdk1.8中,引入了紅黑二叉查詢樹的資料結構。剛開始產生碰撞時,碰撞處仍然是連結串列結構,當連結串列的長度超過原始碼設定值8以後,該處的連結串列將轉為

Android日曆原始碼淺析

前言:本文在整理過程中由於水平有限,若有不當之處,請指正! 1 常見介面及佈局的實現 1.1 日曆主介面:  日曆主介面是由AllInOneActivity實現,對應四種檢視型別動態載入相應的Fragment實現。各檢視如下: (1) 日檢視:在AllInOneActivi

常用集合HashSet原始碼淺析

類圖 原始碼 構造方法 public class HashSet<E> extends AbstractSet<E> implements

JAVAmap

算法 一個數 就是 idt span .com mov bubuko 占用 1,hashmap,hashtable,ConcurrentHashMap 考問點1,hashmap的數據結構,其數據結構為數組+鏈表,把key進過hash算法得到hashcode, 然後has