1.继承HashMap
LinkedHashMap继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的。同HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序。这也是Linked的含义。结构图如下
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {}
2.Entry
继承了HashMap
的Node
类,同时增加了两个字段,用于双向链表实现,也就是LinkedHashMap
= HashMap
+ 双向链表
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);
}
}
3.主要参数
/**
* 头指针,指向第一个node
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* 尾指针,指向最后一个node
*/
transient LinkedHashMap.Entry<K,V> tail;
/**
* false,插入会将新节点放到尾部,访问无所谓
* true,插入和访问都会将当前节点放置到尾部,尾部代表的是最近访问的数据
*
* @serial
*/
final boolean accessOrder;
accessOrder
测试
public static void main(String[] args) {
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75F, true);
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
map.put("4", 4);
// 正常获取,是按照顺序的
System.out.println(map);//{1=1, 2=2, 3=3, 4=4}
map.get("2");
// accessOrder为true时,访问元素后,就将元素放到链表尾
System.out.println(map);//{1=1, 3=3, 4=4, 2=2}
}
4.构造方法
都是调用了HashMap
的构造方法,这里只处理了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;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
5.维护链表的操作
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;
}
afterNodeAccess
accessOrder为true时,get()
操作,将节点移到链表尾,代码逻辑学过数据结构的都会,将节点从链表中取出放入末尾,然后在增加一些判断
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;
}
}
afterNodeInsertion
这是一个很奇葩的方法,虽然名字是在插入之后进行的维护链表的操作,但是默认实际上这个什么都没做,看代码:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry(first)默认返回false,所以afterNodeInsertion这个方法其实并不会执行
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
为什么不执行也可以呢,这个要到put操作的时候就能看出来了。
那么removeEldestEntry这个方法有什么用呢,看名字可以知道是删除最久远的节点,也就是head节点,这个方法实际是给我们自己扩展的。默认是没有用的,可以在LRU
算法中使用
6.get()
public V get(Object key) {
Node<K,V> e;
// 调用父类的HashMap处理
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
// 为true时,处理节点,将访问的元素放到链表尾部
afterNodeAccess(e);
return e.value;
}
void afterNodeAccess(Node<K,V> p) { }
7.put()
LinkedHashMap没有重写HashMap的put方法,所以执行put操作的时候,还是使用的是HashMap的put方法。那么这样如何保证链表的逻辑呢?
原因就是HashMap的putVal方法中实际调用了维护链表的方法,HashMap
中存在了维护链表的方法,子类LinkedHashMap
重写了
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//........................
//........................
//........................
//如果e为null,代表上面的链表遍历到了最后面,并且是新建节点完成添加
//如果e!=null,代表上面链表中存在key相同的节点,需要替换
if (e != null) { // existing mapping for key
//取出就的value值
V oldValue = e.value;
//判断
if (!onlyIfAbsent || oldValue == null)
//改变节点的值
e.value = value;
// 处理节点
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
// 插入节点后继续处理
afterNodeInsertion(evict);
return null;
}
// Callbacks to allow LinkedHashMap post-actions 叫子类LinkedHashMap重写维护双向链表
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
8.remove()
和put操作一样,也是直接使用HashMap的方法来完成的
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//........................
//........................
//........................
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
// 调用子类维护双向链表
afterNodeRemoval(node);
return node;
}
}
return null;
}
void afterNodeRemoval(Node<K,V> p) { }