1. 是什么
- 使用双向链表+HashMap(数组+链表+红黑树)实现
- 相比于HashMap保存了顺序
- 迭代时输出的顺序是
- 按照插入节点的顺序来输出
- 也可以指定成按照访问的顺序输出(LRU)
2. 使用
| public class LinkedHashMapTest |
| { |
| public static void main(String[] args) |
| { |
| LinkedHashMap<String,Object> map = new LinkedHashMap<>(); |
| map.put("name","zsk"); |
| map.put("age",24); |
| map.put("height", 172L); |
| |
| Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); |
| while (iterator.hasNext()) |
| { |
| Map.Entry<String, Object> entry = iterator.next(); |
| |
| |
| |
| |
| |
| System.out.println(entry); |
| } |
| |
| |
| for (Map.Entry<String, Object> entry : map.entrySet()) |
| { |
| |
| |
| |
| |
| |
| System.out.println(entry); |
| } |
| |
| System.out.println(map.containsKey("name")); |
| System.out.println(map.get("name")); |
| map.remove("name"); |
| System.out.println(map.containsKey("name")); |
| } |
| } |
| public class LinkedHashMapTest |
| { |
| public static void main(String[] args) |
| { |
| LinkedHashMap<String, Object> map = new LinkedHashMap<>(2, 0.75F, true); |
| map.put("1", "a"); |
| map.put("2", "b"); |
| map.put("3", "c"); |
| |
| |
| Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); |
| while (iterator.hasNext()) |
| { |
| Map.Entry<String, Object> entry = iterator.next(); |
| |
| |
| |
| System.out.println(entry); |
| } |
| |
| |
| for (Map.Entry<String, Object> entry : map.entrySet()) |
| { |
| |
| |
| |
| System.out.println(entry); |
| } |
| |
| System.out.println(map.get("1")); |
| for (Map.Entry<String, Object> entry : map.entrySet()) |
| { |
| |
| |
| |
| |
| System.out.println(entry); |
| } |
| } |
| } |
| |
| |
3. 实现
3.1. uml

继承了HashMap,可克隆,可序列化
3.2. 构造方法
| public class LinkedHashMap<K,V> |
| extends HashMap<K,V> |
| implements Map<K,V> |
| { |
| |
| 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); |
| } |
| } |
| |
| |
| |
| transient LinkedHashMap.Entry<K,V> head; |
| transient LinkedHashMap.Entry<K,V> tail; |
| |
| |
| final boolean accessOrder; |
| public LinkedHashMap() { |
| |
| super(); |
| accessOrder = false; |
| } |
| } |
LinkedHashMap继承了HashMap,所以map的get、set、remove等方法都是调用的HashMap的方法,而且LinkedHashMap还增强了HashMap的Node,定义了自己的Entry,加入了双向链表
3.3. put
其实就是调用的HashMap的put方法把插入新的节点或者替换value,只不过多了一些其他操作
| 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; |
| 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) |
| treeifyBin(tab, hash); |
| 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) |
| e.value = value; |
| |
| afterNodeAccess(e); |
| return oldValue; |
| } |
| } |
| ++modCount; |
| if (++size > threshold) |
| resize(); |
| |
| afterNodeInsertion(evict); |
| return null; |
| } |
需要注意的以下几点:
- 12行:LinkedHashMap重写了newNode方法
- 35-42行:更新value之后要维护双向链表中的顺序
- 48行:不管是插入了新的节点还是更新了value,都需要根据情况(removeEldest方法是否返回true)删除链表头节点
3.3.1. 创建LinkedHashMap增强的节点--Entry【既是Node数组的节点又是双向链表的节点】
| 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; |
| } |
3.3.1.1. 创建的时候就把节点插入到双向链表尾部
| private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { |
| LinkedHashMap.Entry<K,V> last = tail; |
| tail = p; |
| |
| if (last == null) |
| head = p; |
| else { |
| p.before = last; |
| last.after = p; |
| } |
| } |
3.3.2. put的节点(不是新插入的而是更新value),需要维护双向链表的顺序【输出的顺序】
| |
| if (e != null) { |
| V oldValue = e.value; |
| if (!onlyIfAbsent || oldValue == null) |
| e.value = value; |
| |
| afterNodeAccess(e); |
| return oldValue; |
| } |
3.3.2.1. 把这个节点移动到双向链表尾部
| 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.3. put的节点(不管是新插入的还是更新value)后判断是否需要删除头节点【最少访问的节点】
| void afterNodeInsertion(boolean evict) { |
| LinkedHashMap.Entry<K,V> first; |
| |
| if (evict && (first = head) != null && removeEldestEntry(first)) { |
| |
| K key = first.key; |
| removeNode(hash(key), key, null, false, true); |
| } |
| } |
3.4. get
其实就是调用HashMap的getNode方法获取节点,然后在调用LinkedHashMap的afterNodeAccess把访问的当前节点放到链表的末尾
| 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; |
| } |
- 7-8行:如果设置按照访问顺序的话,那么调用LinkedHashMap的afterNodeAccess把当前访问的节点移动到双向链表的末尾(最新的节点)
3.4.1. get节点后需要维护双向链表的顺序【输出的顺序】
| |
| if (accessOrder) |
| afterNodeAccess(e); |
3.4.1.1. 把访问的当前节点放到链表的末尾
- LinkedHashMap afterNodeAccess
同put操作一样,就是把访问的当前节点放到链表的末尾
| 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.5. containsKey
就是调用HashMap的containsKey方法
| public boolean containsKey(Object key) { |
| |
| return getNode(hash(key), key) != null; |
| } |
3.6. containsValue
重写了HashMap的方法,效率更高
| public boolean containsValue(Object value) { |
| |
| for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) { |
| V v = e.value; |
| if (v == value || (value != null && value.equals(v))) |
| return true; |
| } |
| return false; |
| } |
| |
3.7. remove
其实就是调用HashMap的remove方法删除节点,再调用LinkedHashMap的afterNodeRemoval把当前节点从双向链表中删除
| 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) |
| node = ((TreeNode<K,V>)p).getTreeNode(hash, key); |
| 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) |
| ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); |
| else if (node == p) |
| tab[index] = node.next; |
| else |
| p.next = node.next; |
| ++modCount; |
| --size; |
| |
| afterNodeRemoval(node); |
| return node; |
| } |
| } |
| return null; |
| } |
3.7.1. remove节点后需要从双向链表删除该节点
- LinkedHashMap afterNodeRemoval
| 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; |
| } |
3.8. entrySet
3.8.1. 要研究的代码
| Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator(); |
| public Set<Map.Entry<K,V>> entrySet() { |
| Set<Map.Entry<K,V>> es; |
| |
| |
| return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; |
| } |
调用map.entrySet()返回的是LinkedEntrySet,如下
3.8.2. LinkedHashMap.entrySet()返回的是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(); |
| } |
| public final boolean contains(Object o) { |
| if (!(o instanceof Map.Entry)) |
| return false; |
| Map.Entry<?,?> e = (Map.Entry<?,?>) o; |
| Object key = e.getKey(); |
| Node<K,V> candidate = getNode(hash(key), key); |
| return candidate != null && candidate.equals(e); |
| } |
| public final boolean remove(Object o) { |
| if (o instanceof Map.Entry) { |
| Map.Entry<?,?> e = (Map.Entry<?,?>) o; |
| Object key = e.getKey(); |
| Object value = e.getValue(); |
| return removeNode(hash(key), key, value, true, true) != null; |
| } |
| return false; |
| } |
| public final Spliterator<Map.Entry<K,V>> spliterator() { |
| return Spliterators.spliterator(this, Spliterator.SIZED | |
| Spliterator.ORDERED | |
| Spliterator.DISTINCT); |
| } |
| public final void forEach(Consumer<? super Map.Entry<K,V>> action) { |
| if (action == null) |
| throw new NullPointerException(); |
| int mc = modCount; |
| for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) |
| action.accept(e); |
| if (modCount != mc) |
| throw new ConcurrentModificationException(); |
| } |
| } |
再调用LinkedEntrySet的iterator返回的是LinkedEntryIterator
3.8.3. LinkedHashMap.entrySet().iterator()返回的是LinkedEntryIterator
| final class LinkedEntryIterator extends LinkedHashIterator//继承了LinkedHashIterator |
| implements Iterator<Map.Entry<K,V>> { |
| //重写了next方法,调用LinkedHashIterator的nextNode |
| public final Map.Entry<K,V> next() { return nextNode(); } |
| } |
这个LinkedEntryIterator继承了LinkedHashIterator,如下
3.8.3.1. LinkedEntryIterator继承了LinkedHashIterator
| abstract class LinkedHashIterator { |
| LinkedHashMap.Entry<K,V> next; |
| LinkedHashMap.Entry<K,V> current; |
| int expectedModCount; |
| |
| LinkedHashIterator() { |
| |
| next = head; |
| expectedModCount = modCount; |
| current = null; |
| } |
| |
| public final boolean hasNext() { |
| return next != null; |
| } |
| |
| |
| final LinkedHashMap.Entry<K,V> nextNode() { |
| LinkedHashMap.Entry<K,V> e = next; |
| if (modCount != expectedModCount) |
| throw new ConcurrentModificationException(); |
| if (e == null) |
| throw new NoSuchElementException(); |
| |
| current = e; |
| next = e.after; |
| return e; |
| } |
| |
| public final void remove() { |
| Node<K,V> p = current; |
| if (p == null) |
| throw new IllegalStateException(); |
| if (modCount != expectedModCount) |
| throw new ConcurrentModificationException(); |
| current = null; |
| K key = p.key; |
| |
| removeNode(hash(key), key, null, false, false); |
| expectedModCount = modCount; |
| } |
| } |
| |
可以看出迭代输出的时候是从头到尾输出的,也就是旧的先打印,然后在打印新的节点
4. 总结
在HashMap的基础上+双向链表实现的
迭代LinkedHashMap,就是从内部维护的双链表的表头开始循环输出。
而双链表节点的顺序在LinkedHashMap的get、put时都会更新。以满足按照插入顺序输出,还是访问顺序输出。
5. 参考
原创作者: ThinkerQAQ 转载于: https://www.cnblogs.com/ThinkerQAQ/p/18958338