What is LinkedHashMap?
LinkedHashMap
It is HashMap
the orderly implementation. LinkedHashMap
With a doubly linked list to maintain order, iteration, it also uses the iterator's own implementation.
public static void main(String[] args) {
HashMap<String, Integer> h = new HashMap<>(33);
h.put("one", 1);
h.put("two", 2);
h.put("three", 3);
h.put("four", 4);
for (String key : h.keySet()) {
System.out.println("key:" + key + "value:" + h.get(key));
}
LinkedHashMap<String, Integer> lh = new LinkedHashMap<>(33);
lh.put("one", 1);
lh.put("two", 2);
lh.put("three", 3);
lh.put("four", 4);
for (String key : lh.keySet()) {
System.out.println("key:" + key + "value:" + lh.get(key));
}
}
复制代码
Export
key:twovalue:2
key:threevalue:3
key:fourvalue:4
key:onevalue:1
key:onevalue:1
key:twovalue:2
key:threevalue:3
key:fourvalue:4
复制代码
Underlying array structure
HashMap is a bottom arrays, linked lists, consisting of red-black tree. An array of nodes for storing, linked lists stored hash collision occurs when, after more than a certain length of the list will be optimized for red-black tree.
In addition to inherited from underlying LinkedHashMap HashMap array, linked list, red-black tree, but also more than a doubly linked list links all the nodes (red and green arrow in the figure) for sequentially storing the respective nodes.
Entry of inheritance
LinkedHashMap.Entry
Inherited HashMap.Node
, maintained before and after more than two pointers, two attribute points after the previous Entry and Entry of an Entry, the piece is doubly linked list is used to store the order.
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);
}
}
复制代码
However, no really LinkedHashMap in the HashMap TreeNode override code, regardless of the order that each node is stored in the red-black tree.
We can from the HashMap.TreeNode
inheritance of clues to find out:
Yo roar, the Xiaojia Zi's emotional chaos, a subclass inherits the parent class inside the class, inner class inherits the parent class and subclass of class interior, staged a chicken-egg drama .
// 继承了 LinkedHashMap.Entry
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
}
复制代码
Why HashMap.TreeNode
should inherit LinkedHashMap.Entry
, inherited before and after the pointer has not been used in a HashMap, why not directly inherited HashMap.Node
?
This inheritance is not really designed for the HashMap, HashMap does in no use. But in LinkedHashMap, you can directly use the inherited HashMap.TreeNode
, because this class through inheritance TreeNode already have before and after pointer.
That is why, LinkedHashMap
there is an inherited HashMap.Node
inner class, but did not inherit HashMap.TreeNode
the inner class.
Creation of the list
Creation of the list is the first element into when it started, at the beginning of the list head (head) and tail (tail) are null.
LinkedHashMap
The method does not put override the parent class, the insertion process elements substantially the same, only HashMap
the insertion of a Node
type of node, LinkedHashMap
insert the Entry
types of nodes, and updates the list.
So LinkedHashMap
is how insert nodes, and update the list of it?
// HashMap 中实现
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// HashMap 中实现
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 桶为空时初始化桶
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 取模得到节点在桶中的索引位置,并且该位置没有元素,直接插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 哈希碰撞了,本节不介绍,可以看上一篇讲 HashMap 的文章
else {
// ... 省略部分代码
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
// HashMap 中实现的 newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
// 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 中实现
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
// 如果链表为空,头部和尾部都赋值为p
if (last == null)
head = p;
// 把新插入的节点放在链表尾部
else {
p.before = last;
last.after = p;
}
}
复制代码
Can clearly be seen from the code, the assignment list in the index calculation LinkedHashMap, barrels, or create a hash collision red-black tree realize the use of the HashMap. LinkedHashMap need only create override node, and when creating nodes, update the list storage order. Really is the multiplexing exploits to the extreme.
Delete nodes
As with the insert, delete LinkedHashMap parent class is also used to operate, and then override the callback methods afterNodeRemoval
for maintaining a doubly linked list.
// HashMap 中实现
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
// ... 省略部分代码
}
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;
}
// LinkedHashMap 中覆写
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;
// 如果b为空,则为头节点
if (b == null)
head = a;
else
b.after = a;
// 如果a为空,则为尾节点
if (a == null)
tail = b;
else
a.before = b;
}
复制代码
Maintenance of access order
If we in the initialization LinkedHashMap, the accessOrder parameter set to true, then we are only at the time of insertion maintains the list, while also maintaining access node list.
When we call get, getOrDefault, replace
upon other methods, will update the list, the access node is moved to the tail of the list.
// LinkedHashMap 中覆写
public V get(Object key) {
Node<K,V> e;
// 调用了 HashMap 中的 getNode 方法
if ((e = getNode(hash(key), key)) == null)
return null;
// 如果accessOrder为true,调用afterNodeAccess
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
// 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;
// 如果b为空,则为头部
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
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
复制代码
Use the test code to experience the effects
public static void main(String[] args) {
LinkedHashMap<String, Integer> lh = new LinkedHashMap<>(33, 0.75f, true);
lh.put("one", 1);
lh.put("two", 2);
lh.put("three", 3);
lh.put("four", 4);
lh.get("two");
for (String key : lh.keySet()) {
System.out.println("key:" + key + "value:" + lh.get(key));
}
}
复制代码
Even being given a
Look LinkedHashMap overwrite iterative code
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 = e.after;
return e;
}
复制代码
ConcurrentModificationException
This is to prevent concurrent error conditions, while traversing the list changes occur. Because when we traverse in turn calls the get method, resulting in a change in the list, will throw this error.
accessOrder correct posture when traversing true follows, using LinkedHashMap overwrite forEach
method, it will not modify the order in the list when the read value.
lh.forEach((String k, Integer v) -> {
System.out.println("key:" + k + ", value:" + v);
});
复制代码
LinkedHashMap simple use of LRU
LRU stands for Least Recently Used, which is the least recently used meaning, a memory management algorithm that was first used in the Linux operating system.
This algorithm is based on the assumption: long-term data is not used, the chance of being used in the future is not great. Therefore, when the data reaches a certain threshold percentage of memory, we need to remove the least recently used data.
Here we introduce the pre-knowledge.
afterNodeInsertion
It is a callback method callback to insert elements of the time. LinkedHashMap override this method is mainly used to judge whether to remove the head of the list.
// LinkedHashMap 中覆写
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// 根据条件判断是否移除链表的head节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
// LinkedHashMap 中实现,默认返回false
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
复制代码
Below we will inherit LinkedHashMap, by overwriting removeEldestEntry
, when the number of nodes reached Map exceeds a specified threshold value, the least access to delete nodes. In order to achieve LRU cache policy.
public class SimpleCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_NODE_NUM = 100;
private int limit;
public SimpleCache(){
this(MAX_NODE_NUM);
}
public SimpleCache(int limit) {
super(limit, 0.75f, true);
this.limit = limit;
}
/**
* 判断节点数是否超出限制
* @param eldest
* @return boolean
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > limit;
}
/**
* 测试
*/
public static void main(String[] args) {
SimpleCache<Integer, Integer> cache = new SimpleCache<>(3);
for (int i = 0; i < 10; i++) {
cache.put(i, i * i);
}
System.out.println("插入10个键值对后,缓存内容:");
System.out.println(cache);
System.out.println("访问键值为7的节点后,缓存的内容:");
cache.get(7);
System.out.println(cache);
System.out.println("插入键值为1的键值对后,缓存的内容:");
cache.put(1, 1);
System.out.println(cache);
}
}
复制代码
Test results are as follows:
to sum up
This paper focuses on how to maintain a doubly linked list LinkedHashMap storage order unfolds, introduced inheritance LinkedHashMap and HashMap node classes, introduces new, delete, access, reuse, while LinkedHashMap how HashMap, maintenance doubly linked list. Finally, through inheritance LinkedHashMap very simple implementation of the LRU cache strategy.
More full amount of code, but are relatively easy to understand. JDK understanding of design ideas, explore the realization of the principle behind it, it is a very interesting thing.
The source code discussed in this article are based on JDK1.8 version.
Reference material
LinkedHashMap detailed source code analysis (JDK1.8)
Source series
Java source code Series 1 - ArrayList
Java source code Series 2 - HashMap
Java source code Series 3 - LinkedHashMap
This article first appeared on my personal blog chaohang.top
Author Zhang super
Please indicate the source
I welcome attention to the micro-channel public number [Ultra] will not fly, get updates first time.