LinkedHashMap源码分析

来源:互联网 发布:sails.js 开发api 编辑:程序博客网 时间:2024/05/17 08:50

一、声明

摘自jdk1.8

public class LinkedHashMap<K,V>    extends HashMap<K,V>    implements Map<K,V>

二、概述

LinkedHashMap继承了HashMap,在HashMap基础上,通过维护一个双向链表来保证迭代顺序与插入顺序一致,也可以通过指定参数accessOrder=true,来保证迭代顺序为访问顺序(put、get都算访问)。

Map<String,String> map = new LinkedHashMap<String, String>();map.put("k1","1");map.put(null,"null");map.put("k2","2");map.put("k3","3");map.put("k4","4");for(Map.Entry<String, String> entry : map.entrySet()) {    System.out.println(entry.getKey() + ": " + entry.getValue());}

输出结果:

k1: 1null: nullk2: 2k3: 3k4: 4

LinkedHashMap有两个属性head,tail,是双向链表的头节点和尾节点,其节点是LinkedHashMap.Entry,继承HashMap.Node,before和after分别代表链表中前一个节点引用和后一个节点引用。

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);    }}

LinkedHashMap覆盖了HashMap的newNode,newTreeNode方法,调用put方法时会将HashMap.Node换为LinkedHashMap.Entry(put方法详情可查看上一篇 java8-HashMap源码分析),从而构建出一条双向链表。

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;}// link at the end of listprivate 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;    }}

内存中的存储结构如下:
LinkedHashMap

三、重要方法

HashMap中有三个空实现的方法:

// Callbacks to allow LinkedHashMap post-actions   void afterNodeAccess(Node<K,V> p) { }   void afterNodeInsertion(boolean evict) { }   void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap对这三个方法做了具体实现,从注释和方法名字可以看出它们是 节点访问后、节点插入后、节点移除后的回调方法。

1.afterNodeAccess()

void afterNodeAccess(Node<K,V> e) { // move node to last        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;        }    }

2.afterNodeInsertion()

void afterNodeInsertion(boolean evict) { // possibly remove eldest        LinkedHashMap.Entry<K,V> first;        //当自定义的溢出规则成立,则移除头节点        if (evict && (first = head) != null && removeEldestEntry(first)) {            K key = first.key;            removeNode(hash(key), key, null, false, true);        }    }

removeEldestEntry方法默认是返回false,如果自定义溢出规则,则可以将最近最少访问的节点移除,这就是一个经典的LRU置换算法的实现。

//代码摘自mysql jdbc包package com.mysql.jdbc.util;import java.util.LinkedHashMap;import java.util.Map.Entry;public class LRUCache extends LinkedHashMap {    private static final long serialVersionUID = 1L;    protected int maxElements;    public LRUCache(int maxSize) {        super(maxSize);        this.maxElements = maxSize;    }    protected boolean removeEldestEntry(Entry eldest) {        return this.size() > this.maxElements;    }}

3.afterNodeRemoval()

void afterNodeRemoval(Node<K,V> e) { // unlink        LinkedHashMap.Entry<K,V> p =            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;        //执行remove操作后,将链表中对应节点也删除        p.before = p.after = null;        if (b == null)            head = a;        else            b.after = a;        if (a == null)            tail = b;        else            a.before = b;    }

4.put、get方法
LinkedHashMap并未覆盖put方法。get方法则重新实现并加入了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;    }

这里要特别注意,当设置按访问顺序排序时,调用get方法也会更新链表,因此以下代码迭代会抛出ConcurrentModificationException,应当使用entrySet迭代。

for(Iterator<String> iterator = map.keySet().iterator(); iterator.hasNext();){    String name = iterator.next();    System.out.println(name+":"+map.get(name));}结果:k1:1Exception in thread "main" java.util.ConcurrentModificationException    at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)    at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:742)    at hub.UserVo.main(UserVo.java:32)

四、总结

  1. LinkedHashMap继承HashMap,可以按插入顺序排序,也可以通过设置参数accessOrder=true,按访问顺序排序,其内部通过维护一个双向链表来保证迭代顺序;
  2. 覆盖了afterNodeAccess、afterNodeAccess、afterNodeRemoval三个方法;
  3. 通过指定accessOrder=true,并覆盖removeEldestEntry方法自定义溢出规则可以实现LRUCache。

参考资料:
Java LinkedHashMap工作原理及实现

原创粉丝点击