概述
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;
}
}