注:下面源代码基于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. Qt 文件监视器 QFileSystemWatcher

    之前有过对Qt的QFile以Text纯文本方式进行读取时的学习,这两天由于实时需要又对QFileSystemWatcher(这个类是干什么用的)进行了学习,发现也是问题很让人头疼. 我想监视一个文件夹 ...

  2. Rsync、Unison及DRBD的比较

    一.Rsync Rsync(remote synchronize),顾名思义,可以知道这是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件.Rsync使用所谓的 “Rsync算法”来 ...

  3. Android灭亡论之Firefox OS操作系统出现

    今天是2014年7月1日,过几天就要到深圳实训去了,实训核心内容是Android开发.尽管Android现在很火,但作为程序猿的我们必须时刻保持清醒的头脑.我虽不是什么预言家,但近期接触的Androi ...

  4. WinForm - 格式化DataGridView单元格数据

    效果: 代码: /// <summary> /// 格式化数据 /// </summary> private void dataGridView1_CellFormatting ...

  5. 创建RDD的方式

    创建RDD的方法: JavaRDD<String> lines = sc.textFile("hdfs://spark1:9000/spark.txt");   Jav ...

  6. 三种LVS负载均衡技术的优缺点----负载均衡调度算法

    三种LVS负载均衡技术的优缺点归纳以下表: VS/NATVS/TUNVS/DR 服务器操作系统任意支持隧道多数(支持Non-arp) 服务器网络私有网络局域网/广域网局域网 服务器数目(100M网络) ...

  7. linux eclipse中运行android AVD 错误

    当使用android的AVD时提示以下错误: Starting emulator for AVD 'NexusOne' ERROR: 32-bit Linux Android emulator bin ...

  8. Qt核心剖析:信息隐藏(三篇)

    http://devbean.blog.51cto.com/448512/335550 http://devbean.blog.51cto.com/448512/325581 http://devbe ...

  9. Python与开源GIS:在OGR中使用SQL语句进行查询

    摘要: 属性选择与空间选择都可以看作是OGR内置的选择功能,这两种功能可以解决大部分实际中的问题.但是也有这种时候,就是进行查询时的条件比较复杂.针对这种情况,OGR也提供了更加灵活的解决方案:支持使 ...

  10. (1)ActivityThread分析

    1. 入口. 曾经一直都说Activity的人口是onCreate方法.事实上android上一个应用的入口,应该是ActivityThread.和普通的java类一样,入口是一个main方法. pu ...