本文介绍LinkedHashMap的相关知识
简介
之前了解过HashMap,HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了。
LinkedHashMap由哈希表+双向链表组成,它继承自HashMap,重写了HashMap的一些方法,可以用于LRU算法,它和HashMap一样不是线程安全的。
public class TestLinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>(16,0.75f,true);
linkedHashMap.put("name1", "josan1");
linkedHashMap.put("name2", "josan2");
linkedHashMap.put("name3", "josan3");
System.out.println("LinkedHashMap遍历时顺序:");
for (Entry<String, String> entry : linkedHashMap.entrySet()){
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}
HashMap<String, String> hashMap = new HashMap<String, String>(16);
hashMap.put("name1", "josan1");
hashMap.put("name2", "josan2");
hashMap.put("name3", "josan3");
System.out.println("HashMap遍历时顺序:");
for (Entry<String, String> entry : hashMap.entrySet()){
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}
}
}
结果可知,LinkedHashMap是有序的,且默认为插入顺序。
构造函数
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
public LinkedHashMap() {
super();
//accessOrder默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。
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;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
}
LinkedHashMap 继承了HashMap,实现了Map接口。
LinkedHashMap的accessOrder变量默认为false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。
数据结构
Entry的next是用于维护HashMap指定table位置上连接的Entry的顺序的,before、After是用于维护Entry插入的先后顺序的。
//LinkedHashMap内部类 Entry继承HashMap的Node内部类,是一个双向链表
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);
}
}
该循环双向链表的头部存放的是最久访问的节点或最先插入的节点,尾部为最近访问的或最近插入的节点,迭代器遍历方向是从链表的头部开始到链表尾部结束。
增
LinkedHashMap并没有重写任何put方法。但是其重写了构建新节点的newNode()方法. newNode()会在HashMap的putVal()方法里被调用,putVal()方法会在批量插入数据putMapEntries()或者插入单个数据public V put(K key, V value)时被调用。
LinkedHashMap重写了newNode(),在每次构建新节点时,通过linkNodeLast,将新节点链接在内部双向链表的尾部。
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;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
//tail 指向尾节点即插入的节点
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
HashMap专门预留给LinkedHashMap的afterNodeAccess() afterNodeInsertion() afterNodeRemoval() 方法。
// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
//回调函数,新节点插入之后回调 , 根据evict 和 判断是否需要删除最老插入的节点。如果实现LruCache会用到这个方法。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//LinkedHashMap 默认返回false 则不删除节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//LinkedHashMap 默认返回false 则不删除节点。 返回true 代表要删除最早的节点。通常构建一个LruCache会在达到Cache的上限是返回true
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
删
LinkedHashMap也没有重写remove()方法,因为它的删除逻辑和HashMap并无区别。 但它重写了afterNodeRemoval()这个回调方法。该方法会在Node<K,V> removeNode()方法中回调,removeNode()会在所有涉及到删除节点的方法中被调用。
//双向链表删除节点
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和after 置空
p.before = p.after = null;
//p的前置节点为null,则p是头节点,head指向p的后置节点
if (b == null)
head = a;
else//b不为null,b的后置节点为p的后置节点
b.after = a;
//p的后置节点为null,则p是尾节点,tail指向p的前置节点
if (a == null)
tail = b;
else//a不为null,a的前置节点为p的前置节点
a.before = b;
}
改
更改value时,发生hash冲突,逻辑和HashMap的put逻辑一样。
查
重写了HashMap的get方法,调用getNode方法,LinkedHashMap只是增加了在成员变量(构造函数时赋值)accessOrder为true的情况下,要去回调void afterNodeAccess()函数,在afterNodeAccess()函数中,会将当前被访问到的节点e,移动至内部的双向链表的尾部。
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;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;//原尾节点
//如果accessOrder 是true ,且原尾节点不等于e
if (accessOrder && (last = tail) != e) {
//节点e强转成双向链表节点p
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//p现在是尾节点, 后置节点一定是null
p.after = null;
//如果p的前置节点是null,则p以前是头结点,所以更新现在的头结点是p的后置节点a
if (b == null)
head = a;
else//否则更新p的前直接点b的后置节点为 a
b.after = a;
//如果p的后置节点不是null,则更新后置节点a的前置节点为b
if (a != null)
a.before = b;
else//如果原本p的后置节点是null,则p就是尾节点。 此时 更新last的引用为 p的前置节点b
last = b;
if (last == null) //原本尾节点是null 则,链表中就一个节点
head = p;
else {//否则 更新 当前节点p的前置节点为 原尾节点last, last的后置节点是p
p.before = last;
last.after = p;
}
//尾节点的引用赋值成p
tail = p;
//修改modCount。
++modCount;
}
}
containsValue
LinkedHashMap重写了该方法,相比HashMap的实现,遍历双向链表更为高效。
public boolean containsValue(Object value) {
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
V v = e.value;
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}
对比HashMap,是用两个for循环遍历,相对低效。
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
总结
- LinkedHashMap通过继承HashMap重写了它的一些方法,实现了有序性。
- accessOrder ,默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。为true时,可以在这基础之上构建一个LRUCache。
- LinkedHashMap不是线程安全的,内部结构是哈希表+双向链表。
- LinkedHashMap和HashMap一样,允许一对键值为null,key不能重复,但value可以重复。
参考
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付