1.LinkedHashMap源码分析
1.1 概述
LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。在实现上,LinkedHashMap 很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。
1.2 原理
HashMap的底层结构
LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。其结构可能如下图:
淡蓝色表示前驱,红色箭头表示后继
1.3 源码分析
1.3.1 Entry的继承体系及特性分析
分析一下键值对节点的继承体系。先来看看继承体系结构图
TreeNode不继承它的一个内部类Node,却继承LinkedHashMap的内部类Entry,个人认为是TreeNode在维持红黑树的时候,还会继续维持链表的结构,而且是双向链表,通过prev和next。
①LinkedHashMap和TreeMap都实现了entry的排序,有什么区别:
--TreeMap按照key排序,而LinkedHashMap按照entry插入或者访问顺序排序
--LinkedHashMap保持entry有序方式是调整链表的before,after指针,而treeMap保持entry有序的方式是对tree结构的调整,因此显然LinkedHashMap代价小
②特殊的构造函数LinkedHashMap(int, float,Boolean)
--boolean = true;迭代器顺序遵循LRU原则,最近最少访问的entry会被最先遍历到,这种map结构非常适合构建LRU缓存
③removeEldestEntry(map.entry)
--通过覆写,可以实现:当添加新的映射到map中时,强制自动移除过期的映射。
--过期数据:
----双链表按插入entry排序,则为最早插入双链表的entry
----双链表按访问entry排序,则为最近最少访问的entry
④和hashmap的比较
--增删改查性能比hashmap要差一些,因为要维护双向链表
--迭代器执行时间长短
----LinkedHashMap和size成比例,HashMap和capacity成比例,因此hashmap相对比较费时,以为size<=capacity
⑤三个特殊回调方法
--afterNodeRemoval,删除节点后,双向链表中unlink
--afterNodeInsertion,插入节点后,是否删除eldest节点
--afterNodeAccess,访问节点后,是否调整当前访问节点的顺序
--这三个方法保证了双向链表的有序性,在hashmap中方法体为空,此处进行覆写
⑥为了清晰理解LHM插入节点后的结构,给出一个例子
--hash函数为:h(key)=key%8
--依次插入元素:(k,v)对依次为:(1,11),(2,12),(3,13),(9,19),(17,27)
--给出结构图:(图中node节点未写出value,只写了key)
1.3.2 属性
保存头指针和尾指针 transient LinkedHashMap.Entry<K,V> head; transient LinkedHashMap.Entry<K,V> tail; 通过accessOrder来决定双向链表的排序 final boolean accessOrder; false:构造函数的默认值,表示按照entry的插入顺序进行排序 ,故每插入一个新的entry则添加到双向链表的尾部。(注意:如果插入entry的key之前就存在双向链表中,则此次插入操作只会更改value,不会更改原双向链表各个entry的顺序) true:表示按entry的访问顺序进行排序,根据LRU原则,最新访问的entry排列在双链表的尾部 |
1.3.3 构造函数
指定初始化容量和扩容负载因子,默认按插入顺序 public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } 指定初始化容量,默认按插入顺序 public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } 调用父类的无参构造器 public LinkedHashMap() { super(); accessOrder = false; } 调用父类插入集合的方法putmapentries public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } 初始化容量负载因子,和迭代顺序,false按插入,true按访问 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
|
1.3.4增加元素
LinkedHashMap并没有重写任何put方法,但是重写了构建新节点的newNode方法。newNode方法会在hashMap中的putVal中被调用,putVal方法会在批量插入数据putMapEntries(Map<? Extends k, ? extends v>, Boolean evict)或者插入单个数据public V put (K key, V value)时候被调用
LinkedHashMap重写了newNode方法,每次构建新节点时,通过linkNodeLast(p);将新节点链接在内部双向链表的尾部,创建了一个以null 为节点的entry 在构建新节点时,构建的是`LinkedHashMap.Entry` 不再是`Node` Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p |