09.Java 集合 - LinkedHashMap


基本概念

LinkedHashMap 是 key 键有序的HashMap的一种实现。它除了使用哈希表这个数据结构,使用双向链表来保证key的顺序。

LinkedHashMap 提供了两种 key 的顺序:

  • 访问顺序(access order):可以使用这种顺序实现LRU(Least Recently Used)缓存。
  • 插入顺序(insertion orde):同一 key 的多次插入,并不会影响其顺序。

内部构造

1.继承关系

LinkedHashMap 继承自 HashMap,说明它的内部同样采用哈希表的结构来存储元素。如下图所示:

这里写图片描述


2.Entry

即节点,它是 LinkedHashMap 的内部类,继承自 HashMap.Entry。

在创建该节点时通过调用 HashMap.Entry 的构造函数实现,说明它们的结构一样,是组成单向链表的节点。

private static class Entry<K, V> extends HashMap.Entry<K, V>{

// 构造函数
Entry(int hash, K key, V value, HashMap.Entry<K, V> next) {
super(hash, key, value, next);
}

// 省略部分代码...
}

3.单向链表

上面提到 LinkedHashMap.Entry 可以表示单向链表的节点。那么它是如何实现节点的添加、删除?

// 添加节点
Entry e1 = ...
Entry e2 = new Entry(hash,key,value,e2);

// 移除节点
e2.next = null;

观察代码可知,它通过设置成员变量 next 的值来实现单向链表的基本操作。


4.双向循环链表

LinkedHashMap.Entry 既可以表示单向链表的节点,也可以表示双向循环链表的节点。

它通过两个成员变量: before、after,表示双向循环链表的前后指针。

通过 remove、addBefore 实现双链表节点的加入/移除。

private static class Entry<K, V> extends HashMap.Entry<K, V>{

// 表示前、后指针
Entry<K, V> before, after;

//移除操作
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;
}

// 省略部分代码...
}

操作过程如下图:

  • 删除操作(对应方法注释①②)

这里写图片描述

  • 添加操作(对应方法注释①~④)

这里写图片描述


5.构造函数

LinkedHashMap 在所有的构建过程中都调用了 HashMap 的构造函数来实现。

HashMap 的构造方法都会调用 init 方法执行执行初始化,该方法在这里被重写。

因此 LinkedHashMap 的创建过程为:

LinkedHashMap.construct -> HashMap.construct -> LinkedHashMap.init

下面来看它的源码:

// 按访问顺序排序,否则按照插入顺序排序
private final boolean accessOrder;

public LinkedHashMap() {
super();
accessOrder = false;
}

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

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

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}

// 关键
void init() {
// 创建循环双链表
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}

观察代码,LinkedHashMap 与 HashMap 一样,内部都采用的了哈希表的结构,不同的是:

  • LinkedHashMap 在构建时还会额外的创建一个双循环链表

  • LinkedHashMap 构造函数的入参多了一个参数:accessOrder。该参数表示在访问 LikedHashMap 集合时是否按照访问顺序获取键值对,具体的作用下面会探究到。


操作方法

1.添加操作

在 LinkedHashMap 类中,put 方法表示添加操作。该方法继承自它的父类 HashMap。

put 方法的实现过程是:

  • 先遍历哈希表是否存在匹配的 key,如果有则进行修改操作
  • 若没有通过进行添加操作

在 LinkedHashMap 中类对 addEntry 、createEntry 进行了重写。其调用过程为:

super.put-> addEntry ->super.addEntry -> createEntry

代码如下:

// 1.HashMap.put
public V put(K key, V value) {
// 省略部分代码...

// 不存在相同节点,执行添加操作;该方法在 LinkedHashMap 被重写
addEntry(hash, key, value, i);

return null;
}

// 2.LinkedHashMap.addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);

Entry<K, V> eldest = header.after;

// 默认返回 false
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}

// 3.HashMap.addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
// 省略代码:判断扩容操作,是的话,重新计算哈希表的位置...

// 创建新节点,并插入链表
createEntry(hash, key, value, bucketIndex);
}

// 4.LinkedHashMap.createEntry
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++;
}

2.修改操作

在 LinkedHashMap 类中,put 方法同样表示修改操作。上面提到该方法继承自它的父类 HashMap。

// 1.HashMap.put
public V put(K key, V value) {
// 省略部分代码...

// 遍历该位置上的链表
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;

// 关键,在 HashMap 为空方法,这里对它进行了重写。
e.recordAccess(this);
return oldValue;
}
}

addEntry(hash, key, value, i);
return null;
}

不同于 HashMap 的 Entry,在 LinkedHashMap 中节点对 recordAccess 进行方法进行了重写。

// LinkedHashMap.Entry.recordAccess
void recordAccess(HashMap<K, V> m) {
LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
if (lm.accessOrder) {
lm.modCount++;
// 移除当前节点
remove();
// 添加到双链表的头节点之前,即链表末尾
addBefore(lm.header);
}
}

通过 LinkedHashMap 将操作过的节点转移到双链表的末尾位置,以此实现 LRU。


3.删除操作

在 LinkedHashMap 类中,remove 方法同样表示删除操作。该方法继承自它的父类 HashMap,这里不再分析。


4.查询操作

在 LinkedHashMap 类中,get 方法表示操作查询。

与修改操作一样,不同的是其内部类 Entry 定义的 recordAccess 方法 。

public V get(Object key) {

// HashMap.getEntry
Entry<K, V> e = (Entry<K, V>) getEntry(key);

if (e == null){
return null;
}

// 关键
e.recordAccess(this);
return e.value;
}
本站声明
本文转载自:http://blog.csdn.net/u012420654/article/details/51772293     作者:u012420654     发布日期:2016/06/27     本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。


 
© 2014-2017 ITdaan.com 粤ICP备14056181号