注:下面源代码基于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. Hadoop 2.x(YARN)安装配置LZO

    今天尝试在Hadoop 2.x(YARN)上安装和配置LZO,遇到了很多坑,网上的资料都是基于Hadoop 1.x的,基本没有对于Hadoop 2.x上应用LZO,我在这边记录整个安装配置过程 1. ...

  2. assert使用

    assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义: #include <assert.h> void assert( i ...

  3. android大牛高焕堂最新力作-android架构师之路

    android大牛高焕堂 个人介绍: Android专家顾问,台湾Android论坛主席,现任亚太地区Android技术大会主席,台湾Android领域框架开发联盟总架构师.发表100多篇Androi ...

  4. Simple Addition

    http://acm.hust.edu.cn/vjudge/contest/view.action?cid=31329#problem/V 使用题目所给函数,单单从某一个数字来看,就是直接求这个数各个 ...

  5. T-Sql中的pivot和unpivot

    写在前面 今天休息,抽空了解下pivot和unpivot,记得老师讲行转列的时候,貌似提到过,不过他说的最多的就是“这个你们私下可以自己学,很简单的...”,简单你咋不讲呢,不吐槽他了,还是好好整理下 ...

  6. jsp生命周期和工作原理

    jsp的工作原理jsp是一种Servlet,但是与HttpServlet的工作方式不太一样.httpservlet是先由源代码编译为class文件后部署到服务器下的,先编译后部署.而jsp则是先部署后 ...

  7. ZOJ 3702 Fibonacci

    解题思路: 找规律,不难的,打表 坑的地方在于题目限定条件 and the seed value for G(1) is a random integer t, (t>=1) 虽然都用粗体表示出 ...

  8. docker学习笔记2:容器操作

    一.列出主机上已经创建的容器 docker ps -a 二.创建交互式容器 命令: docker run -i -t ubuntu /bin/bash 其中-i -t 表示创建一个提供交互式shell ...

  9. 基于visual Studio2013解决算法导论之009快速排序随机版本

     题目 快速排序随机版本 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> ...

  10. uva 10313 Pay the Price(完全背包)

    题目连接:10313 - Pay the Price 题目大意:有0~300这300种价值的金额. 现在可能给出参数: 1个:n, 输出可以组成价值n的方式的个数. 2个:n, a输出用个数小于a的价 ...