接口和属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class LinkedHashMap <K,V> extends HashMap <K,V> implements Map <K,V> { transient Entry<K,V> head; transient Entry<K,V> tail; final boolean 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); } } }
构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder) { super (initialCapacity, loadFactor); this .accessOrder = accessOrder; } public LinkedHashMap () { super (); accessOrder = false ; } public LinkedHashMap (Map<? extends K, ? extends V> m) { super (); accessOrder = false ; putMapEntries(m, false ); } public LinkedHashMap (int initialCapacity) { super (initialCapacity); accessOrder = false ; } public LinkedHashMap (int initialCapacity, float loadFactor) { super (initialCapacity, loadFactor); accessOrder = false ; }
插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 ) {...} if ((p = tab[i = (n - 1 ) & hash]) == 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) {...} else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) {...} break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) {...} afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) {...} afterNodeInsertion(evict); return null ; }
LinkedHashMap 重写了 newNode 和回调方法 afterNodeAccess、afterNodeInsertion
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 Node<K,V> newNode (int hash, K key, V value, Node<K,V> e) { Entry<K,V> p = new Entry <K,V>(hash, key, value, e); linkNodeLast(p); return p; } private void linkNodeLast (Entry<K,V> p) { Entry<K,V> last = tail; tail = p; if (last == null ) head = p; else { p.before = last; last.after = p; } } void afterNodeAccess (Node<K,V> e) { Entry<K,V> last; if (accessOrder && (last = tail) != e) { Entry<K,V> p = (Entry<K,V>)e, b = p.before, a = p.after; p.after = null ; if (b == null ) head = a; else b.after = a; if (a != null ) a.before = b; else last = b; if (last == null ) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } } void afterNodeInsertion (boolean evict) { 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 ; }
LinkedHashMap 如何实现 LRU 缓存?
将 accessOrder 设置为 true 并重写 removeEldestEntry 方法当链表大小超过容量时返回 true,使得每次访问一个元素时,该元素会被移动到链表的末尾。一旦插入操作让 removeEldestEntry 返回 true 时,视为缓存已满,LinkedHashMap 就会将链表首元素移除,由此我们就能实现一个 LRU 缓存。
删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public V remove (Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null , false , true )) == null ? null : e.value; } final Node<K,V> removeNode (int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1 ) & hash]) != null ) { Node<K,V> node = null , e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null ) { if (p instanceof TreeNode) {...} else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break ; } p = e; } while ((e = e.next) != null ); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) {...} else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null ; } 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; else b.after = a; if (a == null ) tail = b; else a.before = b; }
根据 hash 定位到桶位置
遍历链表或调用红黑树相关的删除方法
从 LinkedHashMap 维护的双链表中移除要删除的节点
查找
默认情况下,LinkedHashMap 是按插入顺序维护链表,不过我们可以在初始化 LinkedHashMap,指定 accessOrder 参数为 true,即可让它按访问顺序维护链表,当我们调用 get/getOrDefault/replace 等方法时,只要将这些方法访问的节点移动到链表的尾部即可(其前驱和后继也会跟着更新)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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; } 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; } void afterNodeAccess (Node<K,V> e) { LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null ; if (b == null ) head = a; else b.after = a; if (a != null ) a.before = b; else last = b; if (last == null ) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
访问键值为 3 的节点前
访问后,键值为 3 的节点将会被移动到双向链表的最后位置,其前驱和后继也会跟着更新