Java LinkedHashMap类源码解析
摘要: LinkedHashMap繼承了HashMap,他在HashMap的基礎上增加了一個雙向鏈表的結構,鏈表默認維持key插入的順序,重復的key值插入不會改變順序,適用于使用者需要返回一個順序相同的map對象的情況。
LinkedHashMap繼承了HashMap,他在HashMap的基礎上增加了一個雙向鏈表的結構,鏈表默認維持key插入的順序,重復的key值插入不會改變順序,適用于使用者需要返回一個順序相同的map對象的情況。還可以生成access-order順序的版本,按照最近訪問順序來存儲,剛被訪問的結點處于鏈表的末尾,適合LRU,put get compute merge都算作一次訪問,其中put key值相同的結點也算作一次訪問,replace只有在換掉一個鍵值對的時候才算一次訪問,putAll產生的訪問順序取決于原本map的迭代器實現。
在插入鍵值對時,可以通過對removeEldestEntry重寫來實現新鍵值對插入時自動刪除最舊的鍵值對
擁有HashMap提供的方法,迭代器因為是通過遍歷雙向鏈表,所以額外開銷與size成正比與capacity無關,因此選擇過大的初始大小對于遍歷時間的增加沒有HashMap嚴重,后者的遍歷時間依賴與capacity。
同樣是非線程安全方法,對于LinkedHashMap來說,修改結構的操作除了增加和刪除鍵值對外,還有對于access-order時進行了access導致迭代器順序改變,主要是get操作,對于插入順序的來說,僅僅修改一個已有key值的value值不是一個修改結構的操作,但對于訪問順序,put和get已有的key值會改變順序。迭代器也是fail-fast設計,但是fail-fast只是一個調試功能,一個設計良好的程序不應該出現這個錯誤
因為HashMap加入了TreeNode,所以現在LinkedHashMap也有這個功能
以下描述中的鏈表,若無特別說明都是指LinkedHashMap的雙向鏈表
先來看一下基本結構,每個鍵值對加入了前后指針,集合加入了頭尾指針來形成雙向鏈表,accessOrder代表鏈表是以訪問順序還是插入順序存儲
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);}}/*** The head (eldest) of the doubly linked list.頭部*/transient LinkedHashMap.Entry<K,V> head;/*** The tail (youngest) of the doubly linked list.尾部*/transient LinkedHashMap.Entry<K,V> tail;//true訪問順序 false插入順序final boolean accessOrder; 復制代碼然后是幾個內部方法。linkNodeLast將p連接到鏈表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;//原本鏈表為空則p同時為頭部else {p.before = last;last.after = p;}} 復制代碼transferLinks用dst替換src
private void transferLinks(LinkedHashMap.Entry<K,V> src,LinkedHashMap.Entry<K,V> dst) {LinkedHashMap.Entry<K,V> b = dst.before = src.before;LinkedHashMap.Entry<K,V> a = dst.after = src.after;if (b == null)head = dst;elseb.after = dst;if (a == null)tail = dst;elsea.before = dst;} 復制代碼reinitialize在調用HashMap方法的基礎上,將head和tail設為null
void reinitialize() {super.reinitialize();head = tail = null;} 復制代碼newNode生成一個LinkedHashMap結點,next指向e,插入到LinkedHashMap鏈表末端
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);//新建一個鍵值對,next指向elinkNodeLast(p);//p插入到LinkedHashMap鏈表末端return p;} 復制代碼replacementNode根據原結點生成一個LinkedHashMap結點替換原結點
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;LinkedHashMap.Entry<K,V> t =new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);//生成一個新的鍵值對next是給出的next參數transferLinks(q, t);//用t替換qreturn t;} 復制代碼newTreeNode生成一個TreeNode結點,next指向next,插入到LinkedHashMap鏈表末端
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);//生成一個TreeNode,next指向參數nextlinkNodeLast(p);//p插入到LinkedHashMap鏈表末端return p;} 復制代碼replacementTreeNode根據結點p生成一個新的TreeNode,next設為給定的next,替換原本的p
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);transferLinks(q, t);//根據結點p生成一個新的TreeNode,next設為給定的next,替換原本的preturn t;} 復制代碼afterNodeRemoval從LinkedHashMap的鏈上移除結點e
void afterNodeRemoval(Node<K,V> e) { LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;} 復制代碼afterNodeInsertion可能移除最舊的結點,需要evict為true同時鏈表不為空同時removeEldestEntry需要重寫
void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {//removeEldestEntry需要重寫才從發揮作用,否則一定返回falseK key = first.key;//移除鏈表頭部的結點removeNode(hash(key), key, null, false, true);}} 復制代碼afterNodeAccess在訪問過后將結點e移動到鏈表尾部,需要Map是access-order,若移動成功則增加modCount
void afterNodeAccess(Node<K,V> e) { LinkedHashMap.Entry<K,V> last;if (accessOrder && (last = tail) != e) {//Map是access-order同時e不是鏈表的尾部LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;if (b == null)//將結點e從鏈表中剪下head = a;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;//結點e移動到鏈表尾部++modCount;//因為有access-order下結點被移動,所以增加modCount}} 復制代碼構造函數方面,accessOrder默認是false插入順序,初始大小為16,負載因子為0.75,這里是同HashMap。復制構造也是調用了HashMap.putMapEntries方法
containsValue遍歷鏈表尋找相等的value值,這個操作一定不會造成結構改變
public boolean containsValue(Object value) {for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {//檢查同樣是根據LinkedHashMap提供的鏈表順序進行遍歷V v = e.value;if (v == value || (value != null && value.equals(v)))return true;}return false;} 復制代碼get方法復用HashMap的getNode方法,若找到結點且Map是訪問順序時,要將訪問的結點放到鏈表最后,若沒找到則返回null。而getOrDefault僅有的區別是沒找到時返回defaultValue
public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)//復用HashMap的getNode方法return null;if (accessOrder)afterNodeAccess(e);//access-order時將e放到隊尾return e.value;}public V getOrDefault(Object key, V defaultValue) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return defaultValue;//復用HashMap的getNode方法,若沒有找到對應的結點則返回defaultValueif (accessOrder)afterNodeAccess(e);//access-order時將e放到隊尾return e.value;} 復制代碼clear方法在HashMap的基礎上要把head和tail設為null
public void clear() {super.clear();head = tail = null;} 復制代碼removeEldestEntry在put和putAll插入鍵值對時調用,原本是一定返回false的,如果要自動刪除最舊的鍵值對要返回true,需要進行重寫。比如下面這個例子,控制size不能超過100
private static final int MAX_ENTRIES = 100;protected boolean removeEldestEntry(Map.Entry eldest) {return size() > MAX_ENTRIES;} 復制代碼下面兩個方法和HashMap相似,返回key的Set和value的Collection還有返回鍵值對的Set,這個是直接引用,所以對它們的remove之類的修改會直接反饋到LinkedHashMap上
public Set<K> keySet() {Set<K> ks = keySet;if (ks == null) {ks = new LinkedKeySet();keySet = ks;}return ks;//返回key值的set}public Collection<V> values() {Collection<V> vs = values;if (vs == null) {vs = new LinkedValues();values = vs;}return vs;//返回一個包含所有value值的Collection}public Set<Map.Entry<K,V>> entrySet() {Set<Map.Entry<K,V>> es;return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;//返回一個含有所有鍵值對的Set} 復制代碼檢查HashMap的putVal方法,我們可以看到在找到了相同key值并修改value值時會調用afterNodeAccess,對于access-order會改變結點順序
if (e != null) { // 找到了相同的key則修改value值并返回舊的valueV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}復制代碼總結
以上是生活随笔為你收集整理的Java LinkedHashMap类源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 20180917
- 下一篇: Openstack平台搭建(先电版)
