本源码解析是基于JDK1.7,本篇与HashMap源码解析较强的关联性

LinkedHashMap概要

  • LinkedHashMap是基于HashTable与LinkedList原理实现的
  • HashMap是基于数组的,而LinkedHashMap是基于循环双向链表的,即每个节点都有指向前后节点的指针,
  • header节点是不含真实元素的标兵节点,由于每次插入都是在header的前面,header.before指向最新插入的节点(Newest),header.after指向最先插入的节点(Eldest)
  • 迭代次序是确定的,为插入顺序,注意插入相等的key(跟新value)并不会改变迭代的次序
public class Demo {public static void main(String[] args) {Map<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();map.put(1, 2);map.put(2, 3);map.put(1, 2);for (int i : map.keySet()) {System.out.println(map.get(i));}}
} //结果: 2 3 
  • 它可以用来备份map,以保持备份时刻的迭代顺序,同时又不像TreeMap那样引入额外的开销
    void foo(Map m) {Map copy = new LinkedHashMap(m);...}  
  • 允许null键和值
  • 构造器LinkedHashMap(int,float,boolean)可以使得迭代次序为从最近访问的到最后访问的,这种次序通常用来实现LRU(least-recently-used)cache,注意通过集合视图访问不计算在访问次数之内
  • LinkedHashMap可以用来实现遍历时总是首先访问最近访问的节点,但是没有实现LRU cache机制,即删除最老节点的操作(Eldest),下面判断函数总是返回false
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;}  
  • 它的map基本操作时间复杂度与HashMap一样是常数级别的(元素散列合理的情况下),由于需要维护维持迭代次序的LinkedList,通常他会比HashMap效率低,但是遍历整个Map例外,LinkedHashMap遍历时间正比于size,而HashMap正比于capacity,通常capacity>size
  • 与HashMap一样,capacity与loadFactor会影响其基本操作效率,但是由于其遍历时间与capacity无关,所以初始capacity可以设置较大值
  • 非线程安全,可以通过Map m = Collections.synchronizedMap(new LinkedHashMap(...));来实现同步
  • 其迭代器有fail-fast机制,迭代过程中通过迭代器以外的方法改变map结构会抛出异常结束,注意在依据访问次序来决定迭代器顺序的实现中,单纯的get方法也会引起map结构性的改变(需要调整被访问元素的位置)
  • fail-fast机制不能确保并发改变一定抛出异常,只是一种尽可能的校验机制

LinkedHashMap类头部

我们看到他是基于HashMap的实现,实现了Map接口(PS:HashMap实现了Map接口,他不用说也实现了Map接口吧,为啥还要声明。。。)

public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>
public interface Map<K, V> {// Query Operationsint size();boolean isEmpty();boolean containsKey(Object key);boolean containsValue(Object value);V get(Object key);// Modification OperationsV put(K key, V value);V remove(Object key);// Bulk Operationsvoid putAll(Map<? extends K, ? extends V> m);void clear();// ViewsSet<K> keySet();Collection<V> values();Set<Map.Entry<K, V>> entrySet();interface Entry<K, V> {K getKey();V getValue();V setValue(V value);boolean equals(Object o);int hashCode();}// Comparison and hashingboolean equals(Object o);int hashCode();
}  

基本节点 Entry

对比于HashMap其添加了前后指针来支持循环双向链表操作,并用链表来保证访问次序。并为实现LRU机制添加了记录访问的方法,移动到Map的head头部

  • 继承了HashMap的Entry并添加了头尾指针两个域
  • 添加了将当前节点添加到某个节点之前的操作(如果是单链表无法做到)
  • recordAccess通过将当前节点移动到队头来实现最近访问迭代次序最前
    private static class Entry<K,V> extends HashMap.Entry<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {super(hash, key, value, next);}private void remove() {before.after = after;after.before = before;}private void addBefore(Entry<K,V> existingEntry) {after  = existingEntry;before = existingEntry.before;before.after = this;after.before = this;}void recordAccess(HashMap<K,V> m) {LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;if (lm.accessOrder) {lm.modCount++;remove();addBefore(lm.header);}}void recordRemoval(HashMap<K,V> m) {remove();}}  

关键成员变量

LinkedHashMap既是基于数组的也是基于循环双向链表的,用数组来定位元素,用链表来记录相对顺序,header是不含真实元素的标兵节点,由于每次都是在header的前面插入,所以其after指向最先添加元素,before指向最新添加元素

  • 由于其继承了HashMap且与HashMap处于同一package中,所以继承了HashMap的所有非Private成员,其元素查找机制与HashMap一样也是基于Hash的
  • 增加了header成员,于是遍历时不用扫描整个数组,时间复杂度由与capacity成正比变为与size成正比
  • accessOrder用来标志是否是基于LRU机制的访问顺序
    private transient Entry<K,V> header;private final boolean accessOrder;  // 继承自HashMap的成员static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int MAXIMUM_CAPACITY = 1 << 30; transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;  static final Entry<?,?>[] EMPTY_TABLE = {};transient int size; int threshold;final float loadFactor;transient int modCount; 

构造函数

  • 其所有初始化机制都是与HashMap相同的,只是同时初始化了accessOrder标志
  • 在HashMap中定义了一个空的函数init(),并由构造器来负责调用,就是为了给后续的HashMap的扩展留出初始化的位置,在LinkedHashMap中,初始化了header节点,注意header是不含真实元素的标兵节点,由于始终采用的是头插法(每次在header的前面插入),因此header.before实际指向的是链表的最新插入节点,即Newest,而header.after指向的是最先插入的节点,即Eldest
    public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);accessOrder = false;}public LinkedHashMap(int initialCapacity) {super(initialCapacity);accessOrder = false;}public LinkedHashMap() {super();accessOrder = false;}public LinkedHashMap(Map<? extends K, ? extends V> m) {super(m);accessOrder = false;}public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}  @Overridevoid init() {header = new Entry<>(-1, null, null, null);header.before = header.after = header;}  

相对于HashMap优化的方法

由于添加了循环双向链表机制,所有需要遍历的操作都可以不用再扫描原数组(扫描数组的时间复杂度与capacity和size成正比),而是可以直接扫描所有元素(扫描元素的时间复杂度与size成正比),由于HashMap是基于Hash的,用空间换取时间,所有通常capacity要比size大

transfer函数

  • transfer函数用来在进行扩容时将原数组中的Entry转移到新的Table数组中
  • 改进后的transfer不再遍历数组查找所有元素,而是直接用链表进行遍历
    @Overridevoid transfer(HashMap.Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for (Entry<K,V> e = header.after; e != header; e = e.after) {if (rehash)e.hash = (e.key == null) ? 0 : hash(e.key);int index = indexFor(e.hash, newCapacity);e.next = newTable[index];newTable[index] = e;}}  

containValue函数

  • containValue()用来查询value是否在值集合中,需要遍历map
  • 改进后的遍历机制直接使用循环双向链表,避免了扫描原数组中不含元素的点,提高了效率
  • 由于每次插入都是在header的before,header不含真实元素的标兵节点,循环双向链表中header.after指向最先插入的节点即Eldest,header.before指向最新插入节点
    public boolean containsValue(Object value) {// Overridden to take advantage of faster iteratorif (value==null) {for (Entry e = header.after; e != header; e = e.after)if (e.value==null)return true;} else {for (Entry e = header.after; e != header; e = e.after)if (value.equals(e.value))return true;}return false;}  

添加元素

  • addEntry在调用put等后添加元素的方法时调用,需要考虑扩容的问题
  • createEntry用在以已有map创建新map时用,由于此时HashMap的大小已经根据原Map大小确定,所以不用担心扩让的问题,其扩容后也是调用createEntry方法创建新节点
  • 扩容机制与HashMap一致,addEntry只是检测是否适用LRU缓存机制来删除最老的元素,addEntry添加了删除最老节点的机制,但是由于removeEldestEntry(node)总是返回false,所以不会有任何操作
  • createEntry方法与原方法的区别在于调用addBefore()方法来连接新加入节点前后的指针
    void addEntry(int hash, K key, V value, int bucketIndex) {super.addEntry(hash, key, value, bucketIndex);// Remove eldest entry if instructedEntry<K,V> eldest = header.after;if (removeEldestEntry(eldest)) {removeEntryForKey(eldest.key);}}void createEntry(int hash, K key, V value, int bucketIndex) {HashMap.Entry<K,V> old = table[bucketIndex];Entry<K,V> e = new Entry<>(hash, key, value, old);table[bucketIndex] = e;e.addBefore(header);size++;}  

get方法

  • get(key)方法使用与HashMap相同的hash定位机制
  • 由于get方法访问了元素,如果LinkedHashMap初始化为基于最近访问的迭代次序(accessOrder==true),重写的get(key)方法还需要将被访问的元素移动到header之前的位置
    public V get(Object key) {Entry<K,V> e = (Entry<K,V>)getEntry(key);if (e == null)return null;e.recordAccess(this);return e.value;}void recordAccess(HashMap<K,V> m) {LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;if (lm.accessOrder) {lm.modCount++;remove();addBefore(lm.header);}}  

清空方法

重写的方法除了清空数组外还需要清空链表,只剩下不含真实元素的标兵节点 header

    public void clear() {super.clear();header.before = header.after = header;}  

迭代器机制

  • 其他迭代器都是基于LinkedHashIterator的
  • 遍历的起点是header.after即最先插入的节点
  • 遍历结束标志当前节点的after指向了标兵节点header
  • next()类的方法就是简单的返回链表下一个元素
    private abstract class LinkedHashIterator<T> implements Iterator<T> {Entry<K,V> nextEntry    = header.after;Entry<K,V> lastReturned = null;int expectedModCount = modCount;public boolean hasNext() {return nextEntry != header;}public void remove() {if (lastReturned == null)throw new IllegalStateException();if (modCount != expectedModCount)throw new ConcurrentModificationException();LinkedHashMap.this.remove(lastReturned.key);lastReturned = null;expectedModCount = modCount;}Entry<K,V> nextEntry() {if (modCount != expectedModCount)throw new ConcurrentModificationException();if (nextEntry == header)throw new NoSuchElementException();Entry<K,V> e = lastReturned = nextEntry;nextEntry = e.after;return e;}}private class KeyIterator extends LinkedHashIterator<K> {public K next() { return nextEntry().getKey(); }}private class ValueIterator extends LinkedHashIterator<V> {public V next() { return nextEntry().value; }}private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {public Map.Entry<K,V> next() { return nextEntry(); }}Iterator<K> newKeyIterator()   { return new KeyIterator();   }Iterator<V> newValueIterator() { return new ValueIterator(); }Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }  

注意点

  • LinkedHashMap并没有重写remove(key)方法,那么在删除元素后是怎么连接删除元素前后的节点的?
    在HashMap中每次调用remove()方法时都会调用删除节点的e.recordRemoval(this)方法,HashMap的Entry中该方法留空,而在LinkedHashMap中实现了该方法,完成了连接,虽然HashMap并不需要删除后处理连接但是还是预留了这种框架
    final Entry<K,V> removeEntryForKey(Object key) {if (size == 0) {return null;}int hash = (key == null) ? 0 : hash(key);int i = indexFor(hash, table.length);Entry<K,V> prev = table[i];Entry<K,V> e = prev;while (e != null) {Entry<K,V> next = e.next;Object k;if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {modCount++;size--;if (prev == e)table[i] = next;elseprev.next = next;e.recordRemoval(this);return e;}prev = e;e = next;}return e;}  void recordRemoval(HashMap<K,V> m) {remove();} private void remove() {before.after = after;after.before = before;} 
  • 在LinkedHashMap继承了HashMap,其Entry继承了HashMap.Entry,如何保证LinkedHashMap继承的HashMap的方法中的Entry是LinkedHashMap的Entry?
    猜测编译器在编译期将继承自HashMap中的方法编译到LinkedHashMap类中,LinkedHashMap的Entry就成为该类的为一内部类,所以就不存在歧义。
  • 虽然LinkedHashMap禁止了删除Eldest元素的机制,但是通过继承重写protected boolean removeEldestEntry(Entry<String, BucketWriter> eldest)方法就能很方便的实现LRU cache机制
class LRUCacheLinkedHashMap<K, V> extends LinkedHashMap<K, V> {private final int maxElements;public LRUCacheLinkedHashMap(int maxElements) {super(16, 0.75f, true);this.maxElements = maxElements;}@Overrideprotected boolean removeEldestEntry(Entry<K, V> eldest) {if (size() >= maxElements) {return true;} else {return false;}}
}  
  • HashMap中留空了很多方法,如 init(),recordRemoval()等,虽然在HashMap中无用,但是LinkedHashMap实现时却可以直接使用原架构,而只是填充这些小的空方法就可以方便实现

java LinkedHashMap源码解析相关推荐

  1. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

  2. Java Executor源码解析(7)—Executors线程池工厂以及四大内置线程池

    详细介绍了Executors线程池工具类的使用,以及四大内置线程池. 系列文章: Java Executor源码解析(1)-Executor执行框架的概述 Java Executor源码解析(2)-T ...

  3. Java SPI 源码解析及 demo 讲解

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:Java实现QQ登录和微博登录个人原创+1博客:点击前往,查看更多 作者:JlDang 来源:https://s ...

  4. Java HashSet源码解析

    本解析源码来自JDK1.7,HashSet是基于HashMap实现的,方法实现大都直接调用HashMap的方法 另一篇HashMap的源码解析文章 概要 实现了Set接口,实际是靠HashMap实现的 ...

  5. 面试必备:LinkedHashMap源码解析(JDK8)

    1 概述 在上文中,我们已经聊过了HashMap,本篇是基于上文的基础之上.所以如果没看过上文,请先阅读面试必备:HashMap源码解析(JDK8)  本文将从几个常用方法下手,来阅读LinkedHa ...

  6. linkedHashMap源码解析(JDK1.8)

    引言 关于java中的不常见模块,让我一下子想我也想不出来,所以我希望以后每次遇到的时候我就加一篇.上次有人建议我写全所有常用的Map,所以我研究了一晚上LinkedHashMap,把自己感悟到的解释 ...

  7. Java String源码解析

    String类概要 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现 String类提供了操作 ...

  8. Java Thread 源码解析

    Thread 源码解析 线程的方法大部分都是使用Native使用,不允许应用层修改,是CPU调度的最基本单元.线程的资源开销相对于进程的开销是相对较少的,所以我们一般创建线程执行,而不是进程执行. T ...

  9. Java - HashSet源码解析

    java提高篇(二四)-----HashSet 一.定义 public class HashSet<E> extends AbstractSet<E> implements S ...

  10. Java集合源码解析

    文章目录 1.集合包 1.1 ArrayList 实现方式 创建:ArrayList() 插入对象:add(E) 删除对象:remove(E) 获取单个对象:get(int) 遍历对象:iterato ...

最新文章

  1. 学术论文常用英文句型
  2. 电脑销售技巧_汝州市,如何查询楼盘详细销售数据?官方公布,精确到房间
  3. 云图说|不要小看不起眼的日志,“小日志,大作用”
  4. 常见排序算法之基数排序
  5. 微信小程序的在线学习每日签到打卡 项目源码介绍
  6. GoFrame+vue的前后端分离开源项目
  7. 宝塔linux面板-安装golang环境,宝塔面板Linux环境-安装Golang:Go语言环境
  8. Visio如何裁剪图片
  9. Stanford Algorithms: Design and Analysis, Part 1 [Final Exam]
  10. 最简单的人脸检测(免费调用百度AI开放平台接口)
  11. LearnOpenGL学习笔记——法线贴图
  12. python写圆柱的体积_继承实现圆柱体面积体积的计算
  13. 为什么智能手机中被撕的永远是华为!
  14. mysql定时执行任务
  15. java中 数组声明,java数组声明格式
  16. pandas 数据怎样实现行间计算
  17. 新劳动法年假计算工具
  18. ip地址转换数字函数 iton_数字转IP地址函数
  19. 以明道云激起高校学生运动热潮
  20. Java、显示分期还贷时间表

热门文章

  1. python基础篇——列表与列表算法(下)
  2. Android下磁盘分区表损坏,电脑硬盘分区表损坏怎么修复?电脑硬盘分区表损坏的修复方法...
  3. html table 显示最后一条,漂亮CSS表格(Table),最后一行是汇总行【实例】
  4. php mysql pdo 多次_一次php脚本执行过程中多次实例化PDO的情景分析
  5. java--小示例:-2:优化版本,多个文件的调用。
  6. crontab关于 >/dev/null 2>1输出重定向问题
  7. oracle 表复制 long,关于oracle的数据库的数据Long和Number的转化字段
  8. 怎样设置电脑壁纸_怎样把C盘设置成禁止安装任何软件?教你两个方法,告别电脑卡顿...
  9. GPU GEMS 3 EBOOK下载
  10. 蚂蚁金服“定损宝”现身AI顶级会议NeurIPS