注:下面源代码基于jdk1.7.0_11

之前的两篇文章通过源代码分析了两种常见的Map集合,HashMap和Hashtable。本文将继续介绍还有一种Map集合——LinkedHashMap。

顾名思义,LinkedHashMap除了是一个HashMap之外。还带有LinkedList的特点。也就是说可以保持遍历的顺序和插入的顺序一致,那么它是怎么做到的呢?以下我们開始分析。
首先看构造器。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
LinkedHashMap直接继承自HashMap,所以拥有HashMap的大部分特性。比方支持null键和值,默认容量为16。装载因子为0.75。非线程安全等等。
可是LinkedHashMap还有非常多个性的地方,以下来看成员变量:
 private transient Entry<K,V> header;//内部双向链表的头结点
/**
*代表这个链表的排序方式,true代表依照訪问顺序。false代表依照插入顺序。
*/
private final boolean accessOrder;
LinkedHashMap比HashMap多了两个成员变量,当中header代表内部双向链表的头结点。后面我们就会发现,LinkedHashMap除了有个桶数组容纳全部Entry之外,另一个双向链表保存全部Entry引用。

历的时候。并非去遍历桶数组,而是直接遍历双向链表,所以LinkedHashMap的遍历时间不受桶容量的限制,这是它和HashMap的重要差别之中的一个。

而这个accessOrder代表的是是否依照訪问顺序,true代表是,默认是插入顺序。所以我们能够将accessOrder置为true来实现LRU算法,这能够用来做缓存。

再看构造器:
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;
}
构造器首先都会调用父类也就是HashMap的构造器来初始化桶数组,而accessOrder之后会被初始化,除了最后面的一个构造器同意指定accessOrder外。其它构造器都默认将accessOrder置为了false。
读者可能非常奇怪,不是还有个header么。这个双向链表为啥不在构造器中初始化呢?这得回到HashMap中查看hashMap的构造器了:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
... ...
init();
}
HashMap构造器最后一步调用了一个init方法,而这个init方法在HashMap中是个空实现,没有不论什么代码。

这事实上就是所谓的“钩子”,详细代码由子类实现,假设子类希望每次构造的时候都去做一些特定的初始化操作,能够选择复写init方法。

我们看到LinkedHashMap中确实复写了init:
 @Override
void init() {
header = new Entry<>(-1, null, null, null);//初始化双向链表
header.before = header.after = header;//不光是双向链表,还是循环链表
}
在init方法中,果然初始化了双向链表。并且我们还发现,这不光是个双向链表,还是个循环链表。



HashMap内部的Entry类并没有before和after指针,也就是说LinkedHashMap自己重写了一个Entry类
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;//前驱、后继指针
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
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();
}
}
这里的Entry选择继承父类的Entry类,也就是说LinkedHashMap中的Entry拥有三个指针。除了前驱后继指针外用于双向链表的连接外。另一个next指针用于解决hash冲突(引用链)。
除此之外,Entry新增了几个方法,remove和addbefore用来操作双向链表不用多说。而recordAccess方法比較特殊,这种方法在HashMap中也是空实现,并在put方法中会调用此方法:
 public V put(K key, V value) {//HashMap的put方法
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);//发生覆盖操作时。会调用此方法
return oldValue;
}
}
... ...
}

此外,在LinkedHashMap的get方法中,也会调用此方法:

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

也就是说,仅仅要涉及到訪问结点。那么就会调用这种方法。

观察该方法的逻辑:假设accessOrder为true,那么会调用addBefore方法将当前Entry放到双向链表的尾部,终于在我们遍历链表的时候就会发现最近最少使用的结点的都集中在链表头部(从最近訪问最少到最近訪问最多的顺序)。这就是LRU。


LinkedHashMap并没有复写put方法,可是却复写了addEntry和createEntry方法,之前分析HashMap的时候我们就知道了。put方法会调用addEntry将键值对挂到桶的某个合适位置,而addEntry又会调用createEntry方法创建一个键值对对象。因而,LinkedHashMap事实上是间接更改了put方法,想想也非常easy理解,LinkedHashMap除了要向桶中加入键值对外,还需向链表中添加键值对,所以必须得改动put方法。
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<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++;
}
createEntry方法会将键值对分别挂到桶数组和双向链表中。
比較有意思的是addEntry方法。它提供了一个可选的操作。我们能够通过继承LinkedHashMap并复写removeEldestEntry方法让该子类能够自己主动地删除近期最少訪问的键值对——这能够用来做缓存!!

LinkedHashMap自己定义了迭代器以及迭代规则。LinkedHashMap是通过内部的双向链表来完毕迭代的。遍历时间与键值对总数成正比,而HashMap遍历时间与容量成正比,所以通常情况下。LinkedHashMap遍历性能是优于HashMap的。可是由于须要额外维护链表。所以折中来看,两者性能相差无几。
 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;
}
}
总结:
1.LinkedHashMap继承自HashMap。具有HashMap的大部分特性,比方支持null键和值。默认容量为16,装载因子为0.75,非线程安全等等;
2.LinkedHashMap通过设置accessOrder控制遍历顺序是依照插入顺序还是依照訪问顺序。当accessOrder为true时。能够利用其完毕LRU缓存的功能;
3.LinkedHashMap内部维护了一个双向循环链表,而且其迭代操作时通过链表完毕的。而不是去遍历hash表。





【源代码】LinkedHashMap源代码剖析的更多相关文章

  1. 【Java集合源代码剖析】LinkedHashmap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985 前言:有网友建议分析下LinkedHashMap的源代码.于是花了一晚上时间 ...

  2. LinkedHashMap源代码阅读

    LinkedHashMap LinkedHashMap内部採用了散列表和链表实现Map接口,并能够保证迭代的顺序,和HashMap不同,其内部维护一个指向全部元素的双向链表,其决定了遍历的顺序,一般是 ...

  3. 图像库---Image Datasets---OpenSift源代码---openSurf源代码

    1.Computer Vision Datasets on the web http://www.cvpapers.com/datasets.html 2.Dataset Reference http ...

  4. MINA2 源代码学习--源代码结构梳理

    一.mina总体框架与案例: 1.总体结构图: 简述:以上是一张来自网上比較经典的图,总体上揭示了mina的结构,当中IoService包括clientIoConnector和服务端IoAccepto ...

  5. 【Java集合源代码剖析】TreeMap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085 前言 本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透Tr ...

  6. Java源代码之LinkedHashMap

    Java源代码之LinkedHashMap 转载请注明出处:http://blog.csdn.net/itismelzp/article/details/50554412 一.LinkedHashMa ...

  7. 【源代码】LruCache源代码剖析

    上一篇分析了LinkedHashMap源代码,这个Map集合除了拥有HashMap的大部分特性之外.还拥有链表的特点,即能够保持遍历顺序与插入顺序一致. 另外.当我们将accessOrder设置为tr ...

  8. Java To CSharp源代码转换

    前言 开发环境 客户端:Unity3D开发(C#) 服务器:Java (基于Java7) 日   期:2016年09月 需求说明 部分服务器的部分逻辑功能在客户端实现一遍,可以简单的理解为服务器的部分 ...

  9. Apple II DOS 源代码发布

    加州山景城的计算机历史博物馆不仅仅展示硬件,还展示软件.博物馆此前已发布了著名软件MacPaint .Photoshop和APL的源代码,现在它公开了1978年的Apple II DOS源代码.源代码 ...

随机推荐

  1. Centos6.5搭建bugzilla

    一.安装httpd. mod_ssl. mysql-server . mysql .php-mysql . gcc . perl* . mod-perl-devel [root@localhost ~ ...

  2. THUSC2015

    这些题目在BZOJ上面有,可惜是权限题.话说BZOJ上面的题目真的挺好的,要不是我穷,早就去弄个号了! 言归正传,今年的题目难度可以由一个名人名言看出: 题目太水.--某某神犇 可是我掂量了一下,发现 ...

  3. YII 路由配置

    伪静态,通过设置server服务,做域名地址的转换工作. urlManager地址美化,通过程序的方式实现地址美化工作. 通过在主配置文件里配置组件来实现: 'components'=>arra ...

  4. NYOJ-开灯问题

    开灯问题 时间限制:3000 ms  |  内存限制:65535 KB 难度: 描写叙述 有n盏灯,编号为1~n.第1个人把全部灯打开,第2个人按下全部编号为2 的倍数的开关(这些灯将被关掉),第3  ...

  5. 手动制作rpm包

    制作RPM包的过程,简单的说,就是为制作过程提供一个“工作车间”,即一个目录,里面需要包含以下几个子目录: BUILD        ————编译相关源码包时的工作目录: RPMS         — ...

  6. Sql Server 循环添加日期--(累加到一个字段中)

    SQL语句: ),) '

  7. struts+hibernate 请求数据库增删改查(小项目实例)

      StudentAction.java package com.action; import java.util.ArrayList; import java.util.List; import j ...

  8. python pyfits

    原链接:http://blog.sina.com.cn/s/blog_a046022d0101a48h.html,全文复制以备出错.   导入pyfits模块:import pyfits        ...

  9. Python 数据分析(二 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识

    Python 数据分析(二) 本实验将学习利用 Python 数据聚合与分组运算,时间序列,金融与经济数据应用等相关知识 第1节 groupby 技术 第2节 数据聚合 第3节 分组级运算和转换 第4 ...

  10. hdu 4614 Vases and Flowers (二分 线段树)

    题目大意:爱丽丝有N个花瓶,每个花瓶最多放一朵花.然后又如下两个操作. 1:A B    从第A个花瓶开始,往后依次插B朵花,直到不能插为止.如果一朵花都不能插入就输出“can.....”,否则输出第 ...