容器源码解析之LinkedHashMap(九)
1、LinkedHashMap的繼承結(jié)構(gòu)
public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>從結(jié)構(gòu)可以看出,LinkedHashMap繼承HashMap并實現(xiàn)了Map接口。
2、LInkedHashMap構(gòu)造函數(shù)
下面幾個是LinkedHashMap的構(gòu)造函數(shù),每個構(gòu)造函數(shù)都是直接調(diào)用父類HashMap的構(gòu)造函數(shù)來完成相應(yīng)的初始化工作。唯一的不同在于對變量:accessOrder 指定為 false。
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();accessOrder = false;putMapEntries(m, false);}public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}構(gòu)造函數(shù)中所提到的accessOrder
/*** The iteration ordering method for this linked hash map: <tt>true</tt>* for access-order, <tt>false</tt> for insertion-order.** @serial*/final boolean accessOrder;根據(jù)注釋,理解如下:
accessOrder,簡單說就是這個用來控制元素的順序,
accessOrder為true: 表示按照訪問的順序來,也就是誰最先訪問,就排在第一位
accessOrder為false表示按照存放順序來,就是你put元素的時候的順序。
3、LinkedHashMap類中的內(nèi)部類Entry
Entry類繼承的是HashMap.Node類,且引入了兩個屬性before/after,HashMap就是利用HashMap.Node類實現(xiàn)的單鏈表,再加上借助一個存儲HashMap.Node的數(shù)組就實現(xiàn)了“數(shù)組鏈表”的結(jié)合體。而LinkedHashMap引入before/after兩個屬性,可以看出,是準(zhǔn)備實現(xiàn)雙向鏈表的,因此LinkedHashMap將是“數(shù)組和雙鏈表”的結(jié)合體。
static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}//下面為HashMap的Node類static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey() { return key; }public final V getValue() { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}4、put方法
既然是集合,肯定會有put方法來往容器中添加元素,在LinkedHashMap搜索了半天,沒有找到,在找put方法的過程中,發(fā)現(xiàn)有g(shù)et方法,怎么會沒有put方法呢??想了下,唯一的可能就是LinkedHashMap繼承了HashMap沒有重寫HashMap中的put方法也。
下面我們貼出HashMap的put方法,看看這個put方法在LinkedHashMap中是如何來工作的。
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//重新開辟一個Node<K,V>的數(shù)組if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;//1------LinkedHashMapafterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();//2、LinkedHashMapafterNodeInsertion(evict);return null;}上面就是HashMap中put方法的代碼,在看LinkedHashMap源碼之前看HashMap的時候,看到put方法中調(diào)用afterNodeAccess(e)和afterNodeInsertion(evict);而這兩個方法在HashMap是兩個空實現(xiàn)的方法:
// Callbacks to allow LinkedHashMap post-actionsvoid afterNodeAccess(Node<K,V> p) { }void afterNodeInsertion(boolean evict) { }當(dāng)時,還在郁悶,為什么調(diào)用了兩個空實現(xiàn)的函數(shù)呢??
現(xiàn)在,看了LinkedHashMap的源碼,原來這兩個函數(shù)是專門給LinkedHashMap重寫用的。只要LinkedHashMap重寫了這兩個函數(shù),也就完成了LinkedHashMap自己的put方法實現(xiàn)。
put方法的思路在HashMap中已經(jīng)分析過了,大致如下:根據(jù)key的hash值得到存儲位置,然后判斷該存儲位置是否已經(jīng)有了元素,如果有了,則在該位置的鏈表中,找是否含有該key,如果有該key,則更新value。如果沒有找到,則將節(jié)點插入到該位置的鏈表頭。
現(xiàn)在,由于針對的是LinkedHashMap,因此思路稍微發(fā)生了點變化,在鏈表中找到key之后調(diào)用了afterNodeAccess函數(shù),LinkedHashMap中的此函數(shù)不再為空,如果沒有找到key,在插入節(jié)點之后返回之前,調(diào)用了afterNodeInsertion方法。
下面我們就來看下這兩個函數(shù)的具體內(nèi)容。
afterNodeAccess(Node
void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.Entry<K,V> last;//accessOrder 為true時,才會進(jìn)入下面if的語句塊中if (accessOrder && (last = tail) != e) {LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;/*調(diào)整鏈表指向,將e的前一個節(jié)點和后一個節(jié)點連接起來*/if (b == null)//前一個節(jié)點為null的情況head = a;elseb.after = a;if (a != null)//后一個節(jié)點不為null的情況a.before = b;elselast = b;//將p節(jié)點放在鏈表的最后面if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}}下面介紹afterNodeInsertion(boolean evict)
從源碼中可以看到,這個函數(shù)相當(dāng)于什么都沒有做。
原因為:removeEldestEntry函數(shù)一直返回false,導(dǎo)致這個函數(shù)afterNodeInsertion的if條件也就一直為false。
因此,不知道這個函數(shù)為什么這么寫,分析了下,由于LinkedHashMap當(dāng)accessOrder為false時,要按照添加元素的順序進(jìn)行維護(hù)鏈表,而HashMap就是直接將新節(jié)點放入到鏈表頭,因此這個函數(shù)也就不需要做什么了。
void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}}protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;}以上就是關(guān)于LinkedHashMap的put方法,
LinkedHashMap與HashMap的區(qū)別真心不大,從put方法上可以看出,唯一的區(qū)別在于,如果我們設(shè)置了accessOrder = true,則會將訪問的節(jié)點放入到鏈表的尾結(jié)點處,其它的都一樣。
5、get方法
get方法的思路雖然對HashMap的get方法進(jìn)行了重寫,但基本與HashMap的思路一致:也是直接調(diào)用getNode獲取到節(jié)點對象,然后返回其值。
但是,在LinkedHashMap中,由于需要有順序需要維護(hù),因此,當(dāng)accessOrder = true 時,則需要調(diào)用afterNodeAccess(e)方法將此節(jié)點放到雙向鏈表的末尾。而如果accessOrder = false.則完全與HashMap類中的get方法一模一樣。
public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null;if (accessOrder)afterNodeAccess(e);return e.value;}6、getOrDefault方法
getOrDefault方法與get方法唯一的區(qū)別在于,如果key不存在,則返回默認(rèn)值而不是返回null。
public V getOrDefault(Object key, V defaultValue) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return defaultValue;if (accessOrder)afterNodeAccess(e);return e.value;}LinkedHashMap類中其它的方法基本與HashMap類中的方法差不多,這里就不再進(jìn)行介紹。
小結(jié)
LinkedHashMap 和hashMap 功能基本一樣,都是維護(hù)的鍵值對集合,連遍歷 以及方法都類似,唯一的區(qū)別在于HashMap 里面的元素是根據(jù)hash值來決定存放位置的,是無序的,而LinkedHashMap 維護(hù)的是一個按順序存放的雙向鏈表,是有序的。
因此,記住,他們的區(qū)別在于:LinkedHashMap是“數(shù)組和雙向鏈表”的結(jié)合體,而HashMap是“數(shù)組和單向鏈表”的結(jié)合體就夠了。
總結(jié)
以上是生活随笔為你收集整理的容器源码解析之LinkedHashMap(九)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 容器源码分析之HashTable(八)
- 下一篇: 容器源码分析之TreeMap(十)