0. 前言

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52684341

通过HashMap、HashTable以及ConCurrentHashMap异同比较一文我们了解了HashMap的内部存储结构以及各种特性,与HashMap相比,因为LinkedHashMap是继承自HashMap,因此LinkedHashMap:

(1)同样是基于散列表实现。

(2)同时实现了Serializable 和 Cloneable接口,支持序列化和克隆。

(3)并且同样不是线程安全的。

区别是其内部维护了一个双向循环链表,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列。

我们在常见的内存泄漏以及解决方案(二)中介绍的LruCache类就是基于LinkedHashMap实现的。

LinkedHashMap 类层次结构如下所示:


1.  LinkedHashMap数据存储格式

下面这张图来自于BridgeGeorge的博客,省的自己画了。

如上图所示,假设LinkedHashMap进行put操作分别将ABCDEFGHIGKL,共12个KV。LinkedHashMap不仅像HashMap那样对其进行基于哈希表和单链表的Entry数组+ next链表的存储方式,而且还结合了LinkedList的优点,为每个Entry节点增加了前驱和后继,并增加了一个为null头结点,构造了一个双向循环链表。

也就是说,每次put进来KV,除了将其保存到对哈希表中的对应位置外,还要将其插入到双向循环链表的尾部。


2.  LinkedHashMap的构造方法

public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}

若未指定初始容量initialCapacity,则默认为使用HashMap的初始容量,即16。若未指定加载因子loadFactor,则默认为0.75。

accessOrder默认为faslse。这里需要介绍一下这个布尔值,它是双向链表中元素排序规则的标志位。

(1)accessOrder若为false,遍历双向链表时,是按照插入顺序排序。

(2)accessOrder若为true,表示双向链表中的元素按照访问的先后顺序排列,最先遍历到(链表头)的是最近最少使用的元素。

后面会详细讲解这个标志位的作用原理。

3.  LinkedHashMap的put操作

3.1  Key已存在的情况

在HashMap的put方法中,在发现插入的key已经存在时,除了做替换工作,还会调用recordAccess()方法,在HashMap中该方法为空。LinkedHashMap覆写了该方法,(调用LinkedHashmap覆写的get方法时,也会调用到该方法),LinkedHashmap并没有覆写HashMap中的put方法,recordAccess()在LinkedHashMap中的实现如下:

void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
//判断accessOrder是否为true
//将当前访问的Entry放置到双向循环链表的尾部,以标明最近访问
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
//双向循环链表中,将当前的Entry插入到existing Entry的前面
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}

3.2  Key不存在的情况

在put新Entry的过程中,如果发现key不存在时,除了将新Entry放到哈希表的相应位置,还会调用addEntry方法,它会调用creatEntry方法,该方法将新插入的元素放到双向链表的尾部,这样做既符合插入的先后顺序,又符合了访问的先后顺序。

//覆写HashMap中的addEntry方法
//在插入的key不存在的情况下,要调用addEntry插入新的Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
//如果有必要,则删除掉该近期最少使用的节点,
//这要看对removeEldestEntry的覆写,由于默认为false,因此默认是不做任何处理
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
} protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
} void createEntry(int hash, K key, V value, int bucketIndex) {
//创建新的Entry,和HashMap一样将其插入到哈希表的相应位置
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
//并将其移到双向链表的尾部
e.addBefore(header);
size++;
}

在上面的addEntry方法中有一个removeEldestEntry方法,这个方法可以被覆写,比如可以将该方法覆写为如果设定的内存已满,则返回true,这样就可以将最近最少使用的节点(header后的节点)删除掉。

这里为了方便对比总结,我把accessOrder标志位的作用原理做了个表,描述了一些操作对双链表中数据结构的影响,哈希表中元素该怎么处理还怎么处理,和HashMap是一致的。

从总结的上表来看,只要是put进来的新元素,不管accessOrder标志位是什么,均将新元素放到双链表尾部,并且可以在需要实现Lru算法时时覆写removeEldestEntry方法,剔除最近最少使用的节点。

还有两种情况,get获取元素、还有put进Key已经存在的元素,即调用recordAccess的这两种情况下,这个时候标志位就起作用了,accessOrder为fasle时,什么也不做,也就是说当我们放入已经存在Key的键值对或get操作时,它在双链表中的位置是不会变的。accessOrder设置为true时,上述两种情况会将相关元素放置到双链表的尾部。在缓存的角度来看,这就是所谓的“脏数据”,即最近被访问过的数据,因此在需要清理内存时(添加进新元素时),就可以将双链表头节点(空节点)后面那个节点剔除。

4.  LinkedHashMap的get操作

//覆写HashMap中的get方法,通过getEntry方法获取Entry对象
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;
}

通过前面的分析,果然在get中,除了正常的get逻辑,还调用了recordAccess()方法,这个方法的逻辑我们刚刚分析过了,和put进的元素key冲突的情况是一样的,这里就不赘述了。

5.  LinkedHashMap的清空操作

//清空HashMap的同时,将双向链表还原为只有头结点的空链表
public void clear() {
super.clear();
header.before = header.after = header;
}

6.  HashMap和LinkedHashMap的关系和比较

(1)LinkedHashMap继承自HashMap,HashMap的属性它都有,什么线程不安全,支持null等等。

(2)LinkedHashMap比HashMap多维护了一个双向循环链表。很明显,如果前面写的你看懂了,那么LinkedHashMap中维护了数据的两种排序方式,一个是基于数据插入顺序,一种是基于Lru算法。这一条可以说是两者最主要的区别了吧。

至此关于LinkedHashMap的源码分析介绍完毕。

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52684341

Java集合——LinkedHashMap源码详解的更多相关文章

  1. Java集合——TreeMap源码详解

    )TreeMap 是一个有序的key-value集合,它是通过红黑树实现的.因为红黑树是平衡的二叉搜索树,所以其put(包含update操作).get.remove的时间复杂度都为log(n). (2 ...

  2. Java集合——ArrayList源码详解

    ) ArrayList 实现了RandomAccess, Cloneable, java.io.Serializable三个标记接口,表示它自身支持快速随机访问,克隆,序列化. public clas ...

  3. Java集合——LinkedList源码详解

    )LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口. AbstractSequentialList为顺序访问的数据存储结构提供 ...

  4. LinkedHashMap源码详解

    序言 本来是不打算先讲map的,但是随着对set集合的认识,发现如果不先搞懂各种map,是无法理解set的.因为set集合很多的底层就是用map来存储的.比如HashSet就是用HashMap,Lin ...

  5. java 基础数据结构源码详解及数据结构算法

    http://www.cnblogs.com/skywang12345/category/455711.html http://www.cnblogs.com/liqiu/p/3302607.html

  6. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

  7. Java源码详解系列(十)--全面分析mybatis的使用、源码和代码生成器(总计5篇博客)

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  8. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  9. 源码详解系列(七) ------ 全面讲解logback的使用和源码

    什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...

随机推荐

  1. 问问javascript

    问题1:在创建新函数(如function P(){};)的时候会自动创建一个原型对象P.prototype(也称作原型属性prototype).当创建一个新对象(此处指非函数对象,在js里面函数也会被 ...

  2. 翻译-ExcelDNA开发文档

    转载自个人主页 前言 翻译开源项目ExcelDNA开发文档 异步处理 ExcelDNA支持两种异步函数: RTD,该函数适用与Excel2003及以上版本,(当你使用ExcelAsyncUtil.*时 ...

  3. 实现vmare虚拟机系统随主机开机自动启动

    服务器主机上的虚拟机每次开机要手动启动是很麻烦的事,so,在网上找到一方法让虚拟机随主机开机自动运行:挺方便的,记录下来: 1.操作环境 主机:windows 2003 虚拟机:centos6 2.下 ...

  4. SINAMICS S120 Parking axis设置,安转拆除或屏蔽电机

    1) P897 Parking axis selection 此参数可以连接到周期通讯的报文中(PZD) 2) 标准报文111中,已经连接此参数

  5. web前端的10个顶级CSS UI开源框架

    随着CSS3和HTML5的流行,我们的WEB页面不仅需要更人性化的设计理念,而且需要更酷的页面特效和用户体验.作为开发者,我们需要了解一些宝贵的CSS UI开源框架资源,它们可以帮助我们更快更好地实现 ...

  6. python 将表格多个列数据放到同一个单元格中

      表格模板: 目的将卡片1到卡片5的所有数据组合起来到一个单元格中如下入F列中(工作中为了避免手动复制粘贴),其余不变,因为数据太多 自己一个一个复制工作效率太低,所以写这个脚本是为了方便自己有需要 ...

  7. 自己写js库,怎么支持AMD

    最近我打算把之前做项目写的一些工具集成到一个js库中,但是库既要在普通环境正常运行,又要在AMD环境下不暴露全局变量.一时间挺头疼的.随即我参考了一些现在流行的库的源码.学着写了一下,感觉还不错. 既 ...

  8. 旧文备份:windows下编译和使用IT++

    1.下载IT++最新版:<a href="http://sourceforge.net/projects/itpp/">http://sourceforge.net/p ...

  9. background和background-image一点小区别

    如果使用background-image属性,则no-repeat不能使用,因为其对background-image不起作用. 可以使用background属性,再设置no-repeat.

  10. PHP:(一)安装并使用PHP

    php的安装分为两个部分:环境安装配置和开发工具 一.集成环境安装配置 (一)安装 选择:wampserver或者xampp 我采用的是xampp. 在http://www.sourceforce.n ...