JDK容器与并发—Map—LinkedHashMap

概述

      带一层环形双链表的HashMap,非线程安全。    

1)在HashMap中迭代器的遍历效率不高,该双链表层就是为了方便键值对的全遍历;

2)在增删改查操作中维护该双链表,所以其性能较HashMap稍低;

3)可以实现简单的LRU缓存功能;

4)迭代器fail-fast;

数据结构

       在HashMap基础上增加一层双链表,其与HashMap层的数组、单链表数据结构没有任何关系:
 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 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);
	}
}

你可能感兴趣的:(JDK容器与并发)