05_LinkedHashMap

05_LinkedHashMap

一. 基本原理和优缺点

LinkedHashMap能记录你插入元素的顺序,在遍历时,能按照插入的顺序给你遍历出来。

LinkedHashMap是HashMap的子类,所以基本的操作与hashmap类似。不过呢,在插入、删除、替换key-value对的时候,LinkedHashMap会维护一个链表结构,专门用来记录key-value对的顺序。当我们遍历LinkedHashMap时,就能按照顺序把key-value来遍历出来。

所以啊,别看LinkedHashMap的名字中带有Linked,其实它的底层仍然是数组实现的。

当删除元素时,它会在双向链表的尾部删除节点,当查询元素时,实际上是在迭代双向链表,从头节点开始迭代。用于维护双向链表的顺序。

二. 源码分析

2.1 put(K key, V value) 初次插入

绝大部分代码逻辑与hashmap完全一样,只不过,LinkedHashMap自己重写了newNode()方法。

Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
	LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
	linkNodeLast(p);
	return p;
}

将节点封装成了LinkedHashMap.Entry对象,然后使用linkNodeLast()挂载节点。

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
	LinkedHashMap.Entry<K,V> last = tail;
	tail = p;
	if (last == null)
		head = p;
	else {
		p.before = last;
		last.after = p;
	}
}

这里可以看到,LinkedHashMap内部维护了两个指针head和tail,分别指向顺序链表的头结点和尾结点。

每当我们向LinkedHashMap内新增一个key-value,就会在顺序链表的末尾挂载一个顺序节点。这个挂载的过程其实就是对双向链表新增一个节点。

2.2 put(K key, V value) 覆盖已经存在的key

如果LinkedHashMap中已经插入了key1-value1,此时我们插入key1-value2,会产生什么后果呢?

答案是,key1在LinkedHashMap内的插入顺序保持不变,但是value被覆盖。

关于覆盖的操作,由于我们熟悉HashMap的源码,所以立刻可以锁定到hashmap的putVal()内如下代码块:

if (e != null) { // existing mapping for key
	V oldValue = e.value;
	if (!onlyIfAbsent || oldValue == null)
		e.value = value;
	afterNodeAccess(e);
	return oldValue;
}

如果在插入之前,能找到这个key对应的节点,那么就用新的value覆盖这个节点内的旧value,接着执行afterNodeAccess(e)。

注意,LinkedHashMap重写了afterNodeAccess(e)。

void afterNodeAccess(Node<K,V> e) { // move node to last
	LinkedHashMap.Entry<K,V> last;
	if (accessOrder && (last = tail) != e) {
		LinkedHashMap.Entry<K,V> p =
			(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
		p.after = null;
		if (b == null)
			head = a;
		else
			b.after = a;
		if (a != null)
			a.before = b;
		else
			last = b;
		if (last == null)
			head = p;
		else {
			p.before = last;
			last.after = p;
		}
		tail = p;
		++modCount;
	}
}

LinkedHashMap有一个非常重要的参数,accessOrder ,它可以通过入参,经过构造函数传入LinkedHashMap。accessOrder默认等于false,这就意味着,你覆盖或者查询这个key,都不会改变key在链表里的顺序。当它等于true时,就截然相反了,每当你覆盖或者查询这个key,LinkedHashMap就会把这个key挪动到顺序链表的末尾。

2.3 remove

大部分的逻辑仍然走的是hashmap的removeNode(),只不过LinkedHashMap重写了afterNodeRemoval( )。

void afterNodeRemoval(Node<K,V> e) { // unlink
	LinkedHashMap.Entry<K,V> p =
		(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
	p.before = p.after = null;
	if (b == null)
		head = a;
	else
		b.after = a;
	if (a == null)
		tail = b;
	else
		a.before = b;
}
上一篇:这段代码有没有优化空间?


下一篇:LruCache里为什么用LinkedHashMap,HashMap可以吗?