带一层环形双链表的HashMap,非线程安全。
1)在HashMap中迭代器的遍历效率不高,该双链表层就是为了方便键值对的全遍历;
2)在增删改查操作中维护该双链表,所以其性能较HashMap稍低;
3)可以实现简单的LRU缓存功能;
4)迭代器fail-fast;
private transient Entry header; // 双链表层的标记节点
private static class Entry extends HashMap.Entry {
// These fields comprise the doubly linked list used for iteration.
Entry before, after; // 双链表层的引用
Entry(int hash, K key, V value, HashMap.Entry next) {
super(hash, key, value, next);
}
// 在双链表层删除该节点
private void remove() {
before.after = after;
after.before = before;
}
// 在双链表层,在指定的existingEntry节点前链接该节点
private void addBefore(Entry existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
// 调用put、get方法时,如果已存在键值对,则会调用该方法。
// 如果LinkedMap是access-ordered的,则会将该节点移到双链表尾端;否则do nothing
// 这里的移动只是在双链表层做,其数组和单链表是不受影响的
void recordAccess(HashMap m) {
LinkedHashMap lm = (LinkedHashMap)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
// 当LinkedHashMap在HashMap层删除节点时,会调用该方法,在双链表层删除该节点
void recordRemoval(HashMap m) {
remove();
}
}
// 双链表层的节点顺序:true表明为按访问顺序;false表明为按插入顺序,为默认值
private final boolean accessOrder;
// 带初始容量、负载因子构造
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
// 带初始容量构造
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
// 无参构造
public LinkedHashMap() {
super();
accessOrder = false;
}
// 带Map构造
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;
}
LinkedHashMap相对于HashMap的额外工作就是维护双链表:
HashMap的基础构造器会调用该方法,header节点的初始化表明双链表层与HashMap中的数据结构层是独立的:
void init() {
header = new Entry<>(-1, null, null, null);
header.before = header.after = header;
}
// 在HashMap的数据结构层及双链表层将新增的节点添加进来
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry old = table[bucketIndex];
Entry e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header); // 双链表层添加
size++;
}
会调用Entry的recordRemoval方法,在双链表层删除当前节点。
会调用Entry的recordAccess方法,accessOrder为true时维护LRU的特性。
基于双链表实现键值对的全遍历。 基础迭代器为LinkedHashIterator,KeyIterator、ValueIterator、、EntryIterator都继承于它。
private abstract class LinkedHashIterator implements Iterator {
Entry nextEntry = header.after; // 第一个节点
Entry lastReturned = null;
int expectedModCount = modCount;
public boolean hasNext() {
return nextEntry != header; // 判断是否到达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 nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (nextEntry == header)
throw new NoSuchElementException();
Entry e = lastReturned = nextEntry;
nextEntry = e.after;
return e;
}
}
如何实现简单的LRU缓存功能?
1)Override removeEldestEntry方法,实现简单的LRU功能:
/* 简单的LRU缓存实现样例:
* private static final int MAX_ENTRIES = 100;
*
* protected boolean removeEldestEntry(Map.Entry eldest) {
* return size() > MAX_ENTRIES;
* }
*/
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
2)采用带accessOrder参数的构造器,设置双链表按访问顺序排序:
accessOrder = true;
3)LinkedHashMap的增改查方法都会调用到Entry的recordAccess方法,将访问到的节点移到双链表尾端,即让其为最新访问;删除方法会调用Entry的recordRemoval方法,在双链表层删除当前节点。这样就维护了LRU的特性。
4)每当有新的键值对增加请求时,addEntry会调用removeEldestEntry方法确认是否要删除最旧的节点(这里的删除会从HashMap的数据结构及双链表层都进行),如果需要则删除。这样就实现了简单的LRU缓存功能。
// 在添加新的键值对时会调用该方法
// 这里联合removeEldestEntry方法实现简单的LRU缓存
// LinkedHashMap的removeEldestEntry方法默认是没有做LRU的
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// 实现简单的LRU缓存
Entry eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}