8.LinkedHashMap源码解析

1.数据结构
LinkedHashMap继承自HashMap,底层数据结构和HashMap的结构一样并且拥有HashMap的所有特性。不同的是,LinkedHashMap新增了"按照插入顺序访问"和"删除最少访问元素策略"。
源码

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    
	//链表头
	transient LinkedHashMap.Entry<K,V> head;
	 
	//链表尾
	transient LinkedHashMap.Entry<K,V> tail;
	 
	//继承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);
	    }
	}
	 
	/**
     * 控制两种访问模式的字段,默认为false
     * true表示按照访问顺序把经常访问的key放到队尾
     * false表示按照插入顺序访问
     */
	final boolean accessOrder;
}

源码解析
从上述Map新增的属性可以看到,LinkedHashMap的数据结构很像把LinkedList的元素换成HashMap的Node,也正是因为增加了这些结构,从而能把Map元素串联起来,形成一个链表,而链表就可以保证顺序,维护元素插入的顺序。

2.新增
LinkedHashMap通过newNode/newTreeNode方法进行节点新增,下面以newNode为例进行源码解析,具体源码如下。

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    
	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;
    }

	private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    
    
        LinkedHashMap.Entry<K,V> last = tail;
        //新增节点等于尾节点
        tail = p;
        //last为空,说明链表为空,首尾节点相等
        if (last == null)
            head = p;
        //链表有数据,直接建立新增节点和上个尾节点之间的前后关系
        else {
    
    
            p.before = last;
            last.after = p;
        }
    }
}

源码解析

  1. LinkedHashMap初始化时,accessOrder为false,就会按照插入顺序提供访问,插入方法使用的是父类HashMap的put方法,不过覆写了put方法,执行中调用的是newNode/newTreeNode和afterNodeAccess方法。
  2. LinkedHashMap通过新增头节点、尾节点,给每个节点增加before、after属性。每次新增,都把节点追加到尾节点,这样就可以保证新增节点是按照顺序插入到链表中的。

3.访问
LinkedHashMap主要通过迭代器进行访问,迭代器初始化的时候,默认从头节点开始访问,在迭代的过程中,不断访问当前节点的after节点即可,迭代器的具体源码如下。

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    
	abstract class LinkedHashIterator {
    
    
		LinkedHashMap.Entry<K,V> next;
		LinkedHashMap.Entry<K,V> current;
		int expectedModCount;
		//初始化时默认从头节点开始访问
		LinkedHashIterator() {
    
    
		   //头节点作为第一个访问的节点
		   next = head;
		   expectedModCount = modCount;
		   current = 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;
		   //通过链表的after结构,找到下一个迭代的节点
		   next = e.after;
		   return e;
		}
	}
}

源码解析

  1. LinkedHashMap只提供了单向访问,即按照插入的顺序从头到尾进行访问,不能像LinkedList那样进行双向访问。
  2. 在新增节点时,已经维护了元素之间的插入顺序了,所以在迭代访问时,只需不断访问当前节点的下一个节点即可。

4.访问最少删除策略
这种策略也叫做LRU(Least recently used,最近最少使用),就是经常访问的元素会被追加到队尾,这样不经常访问的数据自然就靠近队头,然后通过设置删除策略(如当Map元素个数大于某个阈值时),删除头结点,具体源码如下。

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
    
    
	public V get(Object key) {
    
    
	   Node<K,V> e;
	   //调用HashMap的get方法
	   if ((e = getNode(hash(key), key)) == null)
	       return null;
	   //如果设置了LRU策略,就把当前key移动到队尾
	   if (accessOrder)
	       afterNodeAccess(e);
	   return e.value;
	}
	
	//删除很少被访问的元素 
	void afterNodeInsertion(boolean evict) {
    
    
	   //得到元素头节点
	   LinkedHashMap.Entry<K,V> first;
	   //removeEldestEntry来控制删除策略,如果队列不为空,并且删除策略允许删除的情况下,删除头节点
	   if (evict && (first = head) != null && removeEldestEntry(first)) {
    
    
	       K key = first.key;
	       //删除头节点
	       removeNode(hash(key), key, null, false, true);
	   }
	}
}

源码解析

  1. 通过afterNodeAccess方法把当前访问节点移动到队尾,不仅仅是get方法,执行getOrDefault、compute、computeIfAbsent、computeIfPresent、merge方法时,也会这么做。不断把经常访问的节点移动到队尾后,那么靠近队头的节点,自然就是很少被访问的元素了。
  2. LinkedHashMap本身是没有实现put方法的,调用的是HashMap的put方法,但LinkedHashMap实现了put方法中的afterNodeInsertion方法,这个方法实现了删除操作。

猜你喜欢

转载自blog.csdn.net/Jgx1214/article/details/109096664