LinkedHashMap源码解析(基于JDK1.8)
整体数据结构
-
LinkedHashMap
继承自HashMap
,实现了Map
接口,存储元素的基本结构还是数组与单向链表(jdk1.8版本后,当链表长度大于8时,转换为红黑树存储)的结合,从而支持hash桶读写,当有重复的元素添加时只是修改原来的值。(这部分原理和HashMap原理一致)。 -
LinkedHashMap
内所有节点通过双向链表关联,节点的实现是内部类LinkedHashMap.Entry
继承自HashMap.Node
,除了Node 本身有的几个属性外,额外增加了before
和after
用于指向前一个Entry 后一个Entry,以此实现元素之间维持着一条总的双向链表,正是因为这个链表才保证了LinkedHashMap
的有序性。LinkedHashMap
有两个LinkedHashMap.Entry
类型的成员变量head
和tail
,分别表示双向链表表头、表尾,方便检索和遍历。LinkedHashMap
通过重写entrySet()
方法遍历这个双向链表实现有序遍历。PS:双向链表以及遍历的具体实现在JDK1.7和JDK1.8具体实现存在差异。
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
为什么采用双向链表而不是单链表?
答:双链表除了有一个指向下一节点的指针外,还有一个指向前一节点的指针,可以快速找到前一节点,而单链表无法做到这一点,单链表只能单向读取。双链表的删除的时间复杂度更小。参考双链表的删除和插入的时间复杂度
- 单向链表要删除某一节点时,必须要先通过遍历的方式找到前驱节点(通过待删除节点序号或按值查找)。若仅仅知道待删除节点,是不能知道前驱节点的,故单链表的增删操作复杂度为O(n)。
- 双链表(双向链表)知道要删除某一节点p时,获取其前驱节点q的方式为 q = p->prior,不必再进行遍历,故时间复杂度为O(1)。而若只知道待删除节点的序号,则依然要按序查找,时间复杂度仍为O(n)。
- 单、双链表的插入操作,若给定前驱节点,则时间复杂度均为O(1)。否则只能按序或按值查找前驱节点,时间复杂度为O(n)。
- 至于查找,二者的时间复杂度均为O(n)。对于最基本的CRUD操作,双链表优势在于删除给定节点。但其劣势在于浪费存储空间(若从工程角度考量,则其维护性和可读性都更低)。
- 双链表本身的结构优势在于,可以O(1)地找到前驱节点,若算法需要对待操作节点的前驱节点做处理,则双链表相比单链表有更加便捷的优势。
-
LinkedHashMap
有一个成员变量accessOrder
,还有一个三个参数的构造方法public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
第三个参数
accessOrder
如果被赋值为true,作用是为了记录访问的顺序的,一个元素节点如果被访问过了之后,这里true说明我们要把被访问过的节点通过afterNodeAccess
方法将结点移动至双向链表尾部。LruCache
就是通过LinkedHashMap
的这个特性帮我们实现LRU算法,做到最近最少使用的排序。
操作集合
添加元素
调用put
方法,即父类HashMap
的 put
方法,即调用 putVal
方法,其中有三个关键方法 newNode
和newTreeNode
以及afterNodeAccess
。如果不存在key对应的元素,则创建新节点,newNode
是新建普通节点,newTreeNode
是新建红黑树节点。如果已存在key对应的元素,则将value替换为新值,然后调用afterNodeAccess
。具体逻辑参照HashMap的putVal
方法。
LinkedHashMap
重写了这三个方法。
创建新节点的两个方法newNode
和newTreeNode
:
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;
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
这两个方法中都调用了linkNodeLast
方法,即创建新节点以后,将新节点方法双向链表的尾部。
// link at the end of list
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;
}
}
已存在key对应的元素,则将value替换为新值。
将value替换为新值以后,调用afterNodeAccess
方法,这个方法的作用是将节点移动至双向链表尾部,属性tail
被重新赋值。变量accessOrder
是LinkedHashMap
构造方法传入的。
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;
}
}
获取元素
调用 get
方法,即调用父类HashMap
的 get
方法,可以看到当 accessOrder
为 true 时,仍然会调用 afterNodeAccess
方法。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
删除元素
调用 remove
方法,即HashMap
的 remove
方法,即 removeNode
方法,内部调用LinkedHashMap重写的 afterNodeRemoval
方法。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;
}
遍历
public class Test {
public static void main(String[] args) {
Map<String, String> hashMap = new LinkedHashMap<String, String>();
hashMap.put("key1", "value1");
hashMap.put("key2", "value2");
hashMap.put("key3", "value3");
Set<Entry<String, String>> set = hashMap.entrySet();
Iterator<Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Entry<String, String> entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ", value:" + value);
}
}
}
LinkedHashMap
通过重写entrySet()
方法遍历双向链表实现有序遍历。entrySet()
方法返回一个LinkedEntrySet
对象。
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new LinkedEntryIterator();
}
...省略代码...
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
...省略代码...
}
LinkedEntrySet
的iterator()
方法返回一个LinkedEntryIterator
类型的迭代器对象,通过next()
方法调用nextNode()
方法。
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
LinkedEntryIterator
继承了LinkedHashIterator
,这里nextNode()
方法遍历双向链表获取下一个节点,hasNext()
判断是否还有下一个未遍历的节点。
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;//通过next方法接下来即将遍历到的节点
LinkedHashMap.Entry<K,V> current;//通过next方法指向的当前的节点,主要用来进行remove操作
int expectedModCount;//用来防止遍历的时候进行了增删操作
LinkedHashIterator() {
//这个head是LinkedHashMap的成员变量,
//初始化时,让next指向head节点,以便在开始遍历时从头到尾逐个遍历。
next = head;
expectedModCount = modCount;
current = null;
}
//判断是否还有下一个未遍历的节点
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//记录当前正在遍历的节点
current = e;
//让next指向当前正在遍历的节点的的after节点。
//从这里可以看出,整个遍历过程是,从head开始,然后通过after指向下一个节点,一直到尾部tail
next = e.after;
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
参考文章
Android LruCache源码分析,图片缓存算法的实现原理(基于JDK1.7)
LruCache源码解(基于JDK1.7,Android SDK中源码)
用烂的LruCache,你真的完全懂了么?(基于JDK1.8)
如有错误,欢迎指正。