LinkedHashMap原始碼解讀

z1340954953發表於2018-07-11

LinkedHashMap是HashMap的子類,唯一的區別在於LinkedHashMap對順序的維護,是有序

建構函式

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(m);
	accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
					 float loadFactor,
					 boolean accessOrder) {
	super(initialCapacity, loadFactor);
	this.accessOrder = accessOrder;
}

建構函式引數容量,載入因子都繼承hashMap,唯一的區分在於對於屬性accessOrder的維護

/**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    private final boolean accessOrder;

accessOrder欄位表示對順序的維護。true表示按照訪問順序,false表示按照插入順序(插入可能不是在尾部順序新增,可能是按照索引新增,順序可能不一致)

LinkedHashMap構造預設是維護元素插入的順序

linkedHashMap的資料結構的最小單元

private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

        /**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }

在HashMap.Entry的基礎上新增了兩個屬性before,after 表示 順序上的前一個元素的地址和後一個元素的地址,而原next只是表示在一個陣列位置處的連結串列上的,下一個元素位置。


而且,LinkedHashMap對構造器中的init方法重寫

public HashMap(int initialCapacity, float loadFactor) {
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal initial capacity: " +
										   initialCapacity);
	if (initialCapacity > MAXIMUM_CAPACITY)
		initialCapacity = MAXIMUM_CAPACITY;
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal load factor: " +
										   loadFactor);

	this.loadFactor = loadFactor;
	threshold = initialCapacity;
	init();
}
@Override
void init() {
	header = new Entry<>(-1, null, null, null);
	header.before = header.after = header;
}

構造LinkedHashMap的時候就會建立一個header節點,不放在陣列中,只是用來維護順序

存放元素

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        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++;
        addEntry(hash, key, value, i);
        return null;
    }
LinkedHashMap重寫了recordAccess方法
void recordAccess(HashMap<K,V> m) {
	LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
	if (lm.accessOrder) {
		lm.modCount++;
		remove();
		addBefore(lm.header);
	}
}
private void remove() {
	before.after = after;
	after.before = before;
}
private void addBefore(Entry<K,V> existingEntry) {
	after  = existingEntry;
	before = existingEntry.before;
	before.after = this;
	after.before = this;
}
如果accessOrder為true,就是維護訪問的順序
* 呼叫remove方法,將當前元素從順序中移除,使得它的前面和後面形成順序關係,當前元素的順序after、before都沒有指向。
* 後面的addBefore方法,是將當前元素和header節點,和header節點的下一個節點構成順序,結果是加到header前面,也就是連結串列末尾,形成雙向連結串列,就是按照LinkedList的資料結構維護順序

而且,不論是accessOrder是true還是false都是會呼叫addBefore方法,將新加的元素放到連結串列末尾

void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

查詢元素
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;
    }
查詢元素,如果AccessOrder=true,在順序連結串列,從上面的分析可以看出,當前查詢的元素會放到雙向連結串列的末尾。,最不常用的元素會靠近header。
如何進行遍歷
abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

這類是LinkedHashMap實現迭代的最上層的類,如果modCount!=expectedModCount就是丟擲異常,就是fast-fail機制,

這種機制,使得資料結構不能在迭代的期間發生更改,也就不是執行緒安全的。

* LinkedHashMap錯誤的迭代方式,按照hashMap取出key,在獲取值

public static void loopHashMap(LinkedHashMap<String, String> map ){
		Set<String> keySet = map.keySet();
		Iterator<String> iterator = keySet.iterator();
		while(iterator.hasNext()){
			System.out.print(map.get(iterator.next()));
		}
		System.out.println();
	}
public static void main(String[] args) {
		 LinkedHashMap<String, String> lhm = new LinkedHashMap<String, String>(16, 0.75f, true);
		 loopHashMap(lhm);
	}

LinkedHashMap呼叫get方法的時候,如果accessOrder=true,會呼叫recordAccess修改modCount數值,導致modCount!=exceptedModCount丟擲異常,不建議使用key,在獲取值的方式進行遍歷。

* 正確的迭代方式

public static void loopHashMap(LinkedHashMap<String, String> map ){
		Set<Entry<String, String>> entrySet = map.entrySet();
		Iterator<Entry<String, String>> iterator = entrySet.iterator();
		while(iterator.hasNext()){
			System.out.print(iterator.next().getValue());
		}
		System.out.println();
	}
	

驗證LinkedHashMap,在accessOrder為true的順序,當前操作的元素會放到連結串列末尾

public static void main(String[] args) {
		 LinkedHashMap<String, String> lhm = new LinkedHashMap<String, String>(16, 0.75f, true);
		 lhm.put("1", "1");
		 lhm.put("2", "2");
		 lhm.put("3", "3");
		 lhm.put("4", "4");
		 loopHashMap(lhm);
		 lhm.get("1");
		 loopHashMap(lhm);
		 lhm.get("4");
		 loopHashMap(lhm);
	}

輸出結果

第一次遍歷,最後放的元素是4,元素4會放到連結串列末尾

第二次遍歷,先操作元素1,元素1會放到連結串列末尾

第三次遍歷,操作元素4,元素4會放到連結串列末尾

1234
2341
2314

驗證accessOrder為false時候,LinkedHashMap是按照元素的新增順序遍歷

public static void main(String[] args) {
		 LinkedHashMap<String, String> lhm = new LinkedHashMap<String, String>(16, 0.75f, false);
		 lhm.put("1", "1");
		 lhm.put("2", "2");
		 lhm.put("3", "3");
		 lhm.put("4", "4");
		 loopHashMap(lhm);
		 lhm.get("1");
		 loopHashMap(lhm);
		 lhm.get("4");
		 loopHashMap(lhm);
	}

輸出

1234
1234
1234

總結:accessOrder為true,按照元素的訪問順序,get(key)方法會改變順序

          accessOrder為false,按照元素的新增順序,get(key)方法不會改變順序

參考文章:http://www.cnblogs.com/xrq730/p/5052323.html



相關文章