HashMap的無序性與LinkedHashMap的有序性

概述

HashMap

HashMap存儲元素是無序的,在通過Iterator遍歷元素時也是無序的。

 

LinkedHashMap

LinkedHashMap存儲元素是無序的,在通過Iterator遍歷元素時是有序的;put數據的順序和輸出順序是一致的。

LinkedHashMap遍歷元素的有序性,是採用了雙向鏈表來保存節點。

 

HashMap源碼分析

HashMap數據存儲結構

// 主結構 -- 數組
// java.util.HashMap#table
transient Node<K,V>[] table;

// 元素結構之Node
// java.util.HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

// 元素升級結構之TreeNode(紅黑樹)
// java.util.HashMap.TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

 

Iterator

HashMap<String, String> hashMap = new HashMap<>();
for (int i = 0; i < 6; i++) {
	hashMap.put("k-" + i, "v-" + i);
}

// 獲取Iterator實例
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
Map.Entry<String, String> entry = null;
while (iterator.hasNext()) {
	entry = iterator.next();
	System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

java.util.HashMap#entrySet 

public Set<Map.Entry<K,V>> entrySet() {
	Set<Map.Entry<K,V>> es;
	return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}

java.util.HashMap.EntrySet 

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
	public final int size()                 { return size; }
	public final void clear()               { HashMap.this.clear(); }

	// 獲取Iterator實例
	public final Iterator<Map.Entry<K,V>> iterator() {
		return new EntryIterator();
	}

	// 省略多行代碼... 
}

 

java.util.HashMap.EntryIterator 

注意,EntryIterator繼承於HashIterator,Iterator接口主要實現方法都在abstract HashIterator中

final class EntryIterator extends HashIterator
	implements Iterator<Map.Entry<K,V>> {
	public final Map.Entry<K,V> next() { return nextNode(); }
}

java.util.HashMap.HashIterator 

abstract class HashIterator {
	Node<K,V> next;        // next entry to return
	Node<K,V> current;     // current entry
	int expectedModCount;  // for fast-fail
	int index;             // current slot

	HashIterator() {
		expectedModCount = modCount;
		Node<K,V>[] t = table;
		current = next = null;
		index = 0;
		
		// 初始化next節點值 -- 注意此處
		if (t != null && size > 0) { // advance to first entry
			do {} while (index < t.length && (next = t[index++]) == null);
		}
	}

	// 判斷是否有下一個節點
	public final boolean hasNext() {
		return next != null;
	}

	// 獲取下一個節點
	final Node<K,V> nextNode() {
		Node<K,V>[] t;
		Node<K,V> e = next; // 將下一個節點(next節點)賦予e,最後將會返回e節點值
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		if (e == null)
			throw new NoSuchElementException();
		
		// 1. 如果當前節點current.next不爲null時,則將 current.next(這是鏈表上的下一個節點,不是table數組上的元素)存儲到成員變量next中
		if ((next = (current = e).next) == null && (t = table) != null) {
			// 2. current.next == null && table != null
			// index從0開始,index是HashIterator的成員變量。
			// 當index < table.length && table[index++] == null時,說明該index位置上沒有元素,則將會繼續循環查找下一個index位置;
			// 直至 index >= table.length || table[index++] != null 時,將會退出循環,將結果存儲到成員變量next中
			do {} while (index < t.length && (next = t[index++]) == null);
		}
		
		return e;
	}

	// 省略多行代碼... 
}

由nextNode()方法可以看出,HashMap遍歷元素時是無序的。

 

LinkedHashMap源碼分析

LinkedHashMap數據存儲結構(注意和HashMap數據結構對比)

// 主結構 -- 數組
// java.util.HashMap#table
transient Node<K,V>[] table;

// 元素結構之Entry
// java.util.LinkedHashMap.Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
}

// 元素升級結構之TreeNode(紅黑樹)
// java.util.HashMap.TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}


 

Iterator

LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
for (int i = 0; i < 6; i++) {
	linkedHashMap.put("k-" + i, "v-" + i);
}

// 獲取Iterator實例
Iterator<Map.Entry<String, String>> iterator = linkedHashMap.entrySet().iterator();
Map.Entry<String, String> entry = null;
while (iterator.hasNext()) {
	entry = iterator.next();
	System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

java.util.LinkedHashMap#entrySet

public Set<Map.Entry<K,V>> entrySet() {
	Set<Map.Entry<K,V>> es;
	return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

java.util.LinkedHashMap.LinkedEntrySet

final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
	public final int size()                 { return size; }
	public final void clear()               { LinkedHashMap.this.clear(); }
	public final Iterator<Map.Entry<K,V>> iterator() {
		return new LinkedEntryIterator();
	}

	// 省略多行代碼... 
}

java.util.LinkedHashMap.LinkedEntryIterator

 

注意,LinkedEntryIterator繼承於LinkedHashIterator,Iterator接口的主要實現都在abstract LinkedHashIterator中

final class LinkedEntryIterator extends LinkedHashIterator
	implements Iterator<Map.Entry<K,V>> {
	public final Map.Entry<K,V> next() { return nextNode(); }
}

java.util.LinkedHashMap.LinkedHashIterator

abstract class LinkedHashIterator {
	LinkedHashMap.Entry<K,V> next;
	LinkedHashMap.Entry<K,V> current;
	int expectedModCount;

	LinkedHashIterator() {
		// 初始化next,將頭結點(head)賦予next
		next = head;
		
		expectedModCount = modCount;
		current = null;
	}

	// 判斷是否有下一個節點
	public final boolean hasNext() {
		return next != null;
	}

	// 獲取下一個節點
	final LinkedHashMap.Entry<K,V> nextNode() {
	
		// 將next賦予e,將e返回
		LinkedHashMap.Entry<K,V> e = next;
		
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		if (e == null)
			throw new NoSuchElementException();
		current = e;
		
		// next重新賦予新值,存儲當前節點(current = e)的後置節點(after)
		next = e.after;
		
		return e;
	}
	
	// 省略多行代碼... 
}

LinkedHashMap是採用了雙向鏈表來保存節點,當在獲取下一個節點時,會通過after(後置節點)查找,從而達到遍歷的有序性。

接下來在看看LinkedHashMap put的一些源碼細節,將會更明白爲啥after會獲取有序的下一個節點。

LinkedHashMap put

java.util.LinkedHashMap#newNode

/**
 * 創建新的節點
 * @param hash key的hash值
 * @param key
 * @param value
 * @param e 新添加節點的下一個節點,默認是null
 * @return
 */
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
	LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
	
	// 此方法將會把元素的首尾節點記錄下來
	linkNodeLast(p);
	return p;
}

java.util.LinkedHashMap#linkNodeLast

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
	LinkedHashMap.Entry<K,V> last = tail;
	
	// 將新添加的節點p設置爲LinkedHashMap的成員變量tail(tail是當前鏈表的尾節點);
	tail = p;
	
	// 尾結點爲null時,說明沒有任何節點,則將p設置爲頭結點(head)
	if (last == null)
		head = p;
	
	// 
	else {
		// 將當前鏈表的尾結點(last)設置爲 新添加節點p的前置節點(before)
		p.before = last;
		
		// 在將尾結點(last)的後置節點(after)設置爲 新添加節點p
		last.after = p;
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章