9.Java SDK源码分析系列笔记-LinkedHashMap

1. 是什么

  • 使用双向链表+HashMap(数组+链表+红黑树)实现
  • 相比于HashMap保存了顺序
    • 迭代时输出的顺序是
      • 按照插入节点的顺序来输出
      • 也可以指定成按照访问的顺序输出(LRU)

2. 使用

  • 按照插入节点的顺序来输出

   
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap<String,Object> map = new LinkedHashMap<>();
map.put("name","zsk");
map.put("age",24);
map.put("height", 172L);
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, Object> entry = iterator.next();
/*插入的顺序是怎样那么输出就是怎样
* name=zsk
age=24
height=172
* */
System.out.println(entry);
}
//上面的输出跟这个一样
for (Map.Entry<String, Object> entry : map.entrySet())
{
/*插入的顺序是怎样那么输出就是怎样
* name=zsk
age=24
height=172
* */
System.out.println(entry);
}
System.out.println(map.containsKey("name"));//true
System.out.println(map.get("name"));//zsk
map.remove("name");
System.out.println(map.containsKey("name"));//false
}
}
  • 按照访问的顺序输出

   
public class LinkedHashMapTest
{
public static void main(String[] args)
{
LinkedHashMap<String, Object> map = new LinkedHashMap<>(2, 0.75F, true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry<String, Object> entry = iterator.next();
// 1=a
// 2=b
// 3=c
System.out.println(entry);
}
//上面的输出跟这个一样
for (Map.Entry<String, Object> entry : map.entrySet())
{
// 1=a
// 2=b
// 3=c
System.out.println(entry);
}
System.out.println(map.get("1"));//访问了1,那么1所在的节点被移到链表末尾
for (Map.Entry<String, Object> entry : map.entrySet())
{
//最近被访问的节点被放到链表末尾
// 2=b
// 3=c
// 1=a
System.out.println(entry);
}
}
}

3. 实现

3.1. uml


继承了HashMap,可克隆,可序列化

3.2. 构造方法


   
public class LinkedHashMap<K,V>
extends HashMap<K,V>//继承HashMap
implements Map<K,V>
{
//链表的节点。双向链表
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);
}
}
//双向链表的头尾节点
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
//true的话保持访问的顺序,false的话保持插入的顺序
final boolean accessOrder;
public LinkedHashMap() {
//调用HashMap的构造方法
super();
accessOrder = false;
}
}

LinkedHashMap继承了HashMap,所以map的get、set、remove等方法都是调用的HashMap的方法,而且LinkedHashMap还增强了HashMap的Node,定义了自己的Entry,加入了双向链表

3.3. put

其实就是调用的HashMap的put方法把插入新的节点或者替换value,只不过多了一些其他操作

  • HashMap.put

   
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
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)
//LinkedHashMap重写了newNode方法
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//节点已经存在,只是更新value的情况
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//需要维护双向链表中的顺序
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
//removeEldest方法返回true的时候,需要删除头节点【最少访问的节点】
afterNodeInsertion(evict);
return null;
}

需要注意的以下几点:

  • 12行:LinkedHashMap重写了newNode方法
  • 35-42行:更新value之后要维护双向链表中的顺序
  • 48行:不管是插入了新的节点还是更新了value,都需要根据情况(removeEldest方法是否返回true)删除链表头节点
3.3.1. 创建LinkedHashMap增强的节点--Entry【既是Node数组的节点又是双向链表的节点】
  • LinkedHashMap.newNode

   
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//创建的是LinkedHashMap增强的Entry
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
//插入到链表尾部
linkNodeLast(p);
return p;
}
3.3.1.1. 创建的时候就把节点插入到双向链表尾部
  • 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;
}
}
3.3.2. put的节点(不是新插入的而是更新value),需要维护双向链表的顺序【输出的顺序】

   
//节点已经存在,只是更新value的情况
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//需要维护双向链表中的顺序
afterNodeAccess(e);
return oldValue;
}
3.3.2.1. 把这个节点移动到双向链表尾部
  • afterNodeAccess

   
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;
}
}
3.3.3. put的节点(不管是新插入的还是更新value)后判断是否需要删除头节点【最少访问的节点】
  • afterNodeInsertion

   
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry返回true的时候【场景:比如定义LRU算法超过一定容量删除最少访问的节点】
if (evict && (first = head) != null && removeEldestEntry(first)) {
//删除头节点
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}

3.4. get

其实就是调用HashMap的getNode方法获取节点,然后在调用LinkedHashMap的afterNodeAccess把访问的当前节点放到链表的末尾

  • LinkedHashMap.get

   
public V get(Object key) {
Node<K,V> e;
//调用HashMap的getNode获取节点
if ((e = getNode(hash(key), key)) == null)
return null;
//如果设置按照访问顺序的话,那么
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
  • 7-8行:如果设置按照访问顺序的话,那么调用LinkedHashMap的afterNodeAccess把当前访问的节点移动到双向链表的末尾(最新的节点)
3.4.1. get节点后需要维护双向链表的顺序【输出的顺序】

   
//如果设置按照访问顺序的话,那么
if (accessOrder)
afterNodeAccess(e);
3.4.1.1. 把访问的当前节点放到链表的末尾
  • LinkedHashMap afterNodeAccess
    同put操作一样,就是把访问的当前节点放到链表的末尾

   
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
//p代表当前节点,a代表后一个节点,b代表前一个节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//前面节点的next指向后一个节点
p.after = null;
if (b == null)
head = a;
else
b.after = a;
//后一个节点的prev指向前一个节点
if (a != null)
a.before = b;
else
last = b;
//当前节点的prev指向尾节点,尾节点的next指向当前节点
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
//更新尾节点为当前节点
tail = p;
++modCount;
}
}

3.5. containsKey

就是调用HashMap的containsKey方法


   
public boolean containsKey(Object key) {
//依然是HashMap.getNode,没什么特殊的地方
return getNode(hash(key), key) != null;
}

3.6. containsValue

重写了HashMap的方法,效率更高


   
public boolean containsValue(Object value) {
//遍历双向链表,效率O(N),不同于HashMap的O(N2)
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;
}

3.7. remove

其实就是调用HashMap的remove方法删除节点,再调用LinkedHashMap的afterNodeRemoval把当前节点从双向链表中删除

  • HashMap.remove

   
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
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 (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.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;
}
3.7.1. remove节点后需要从双向链表删除该节点
  • LinkedHashMap afterNodeRemoval

   
void afterNodeRemoval(Node<K,V> e) { // unlink
//以下的操作把当前删除的节点从双向链表中删除
//p是当前被删除的节点,b是p的前一个节点,a是p的后一个节点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
//修改前一个节点的next指针
if (b == null)
head = a;
else
b.after = a;
//修改后一个节点的prev指针
if (a == null)
tail = b;
else
a.before = b;
}

3.8. entrySet

3.8.1. 要研究的代码

   
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
  • LinkedHashMap entrySet

   
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
//这个entrySet属性什么时候set的?
//返回LinkedEntrySet
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

调用map.entrySet()返回的是LinkedEntrySet,如下

3.8.2. LinkedHashMap.entrySet()返回的是LinkedEntrySet
  • LinkedEntrySet

   
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() {
//遍历器是LinkedEntryIterator
return new LinkedEntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
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;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}

再调用LinkedEntrySet的iterator返回的是LinkedEntryIterator

3.8.3. LinkedHashMap.entrySet().iterator()返回的是LinkedEntryIterator

   
final class LinkedEntryIterator extends LinkedHashIterator//继承了LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
//重写了next方法,调用LinkedHashIterator的nextNode
public final Map.Entry<K,V> next() { return nextNode(); }
}

这个LinkedEntryIterator继承了LinkedHashIterator,如下

3.8.3.1. LinkedEntryIterator继承了LinkedHashIterator
  • LinkedHashIterator

   
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
//从链表头部开始遍历
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
//next会调用这个方法
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;
}
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;
//HashMap的removeNode方法
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}

可以看出迭代输出的时候是从头到尾输出的,也就是旧的先打印,然后在打印新的节点

4. 总结

在HashMap的基础上+双向链表实现的
迭代LinkedHashMap,就是从内部维护的双链表的表头开始循环输出。
而双链表节点的顺序在LinkedHashMap的get、put时都会更新。以满足按照插入顺序输出,还是访问顺序输出。

5. 参考

原创作者: ThinkerQAQ 转载于: https://www.cnblogs.com/ThinkerQAQ/p/18958338
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值