博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
8,HashMap子类-LinkedHashMap
阅读量:4655 次
发布时间:2019-06-09

本文共 7216 字,大约阅读时间需要 24 分钟。

在上一篇随笔中,分析了HashMap的源码,里面涉及到了3个钩子函数(afterNodeAccess(e),afterNodeInsertion(evict),afterNodeRemoval(node)),用来预设给子类——LinkedHashMap调用。

一,LinkedHashMap数据结构

可以从上图中看到,LinkedHashMap数据结构相比较于HashMap来说,添加了双向指针,分别指向前一个节点——before和后一个节点——after,从而将所有的节点已链表的形式串联一起来。数据结构为(数组 + 单链表 + 红黑树 + 双链表),图中的标号是结点插入的顺序。

二,LinkedHashMap源码

1,LinkedHashMap结构

LinkedHashMap继承HashMap,所以HashMap中的非private方法和字段,都可以在LinkedHashMap直接中访问。

public class LinkedHashMap
extends HashMap
implements Map
{ static class Entry
extends HashMap.Node
{ Entry
before, after; Entry(int hash, K key, V value, Node
next) { super(hash, key, value, next); } } // 版本序列号 private static final long serialVersionUID = 3801124242820219131L; // 链表头结点 transient LinkedHashMap.Entry
head; // 链表尾结点 transient LinkedHashMap.Entry
tail; /** * 用来指定LinkedHashMap的迭代顺序, * true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾 * false则表示按照插入顺序来 */ final boolean accessOrder;}

2,构造函数

LinkedHashMap提供了五种方式的构造器,所有构造函数的第一行都会调用父类构造函数,使用super关键字,如下

构造器一:

public LinkedHashMap(int initialCapacity, float loadFactor) {        super(initialCapacity, loadFactor);        accessOrder = false;}

accessOrder默认为false,access为true表示之后访问顺序按照元素的访问顺序进行,即不按照之前的插入顺序了,access为false表示按照插入顺序访问。

构造器二:

public LinkedHashMap(int initialCapacity) {        super(initialCapacity);        accessOrder = false;}

构造器三:

public LinkedHashMap() {        super();        accessOrder = false;}

构造器四:

public LinkedHashMap(Map
m) { super(); accessOrder = false; putMapEntries(m, false);}

putMapEntries是调用到父类HashMap的函数。

构造器五:

public LinkedHashMap(int initialCapacity,                         float loadFactor,                         boolean accessOrder) {    super(initialCapacity, loadFactor);    this.accessOrder = accessOrder;}

通过指定accessOrder的值,从而控制访问顺序。

3,LinkedHashMap.Entry内部类

static class Entry
extends HashMap.Node
{ Entry
before, after; Entry(int hash, K key, V value, Node
next) { super(hash, key, value, next); }}

LinkedHashMap.Entry继承自HashMap.Node,在HashMap.Node基础上增加了前后两个指针域。

4,部分函数

4.1,get()函数

public V get(Object key) {    Node
e; if ((e = getNode(hash(key), key)) == null) return null; //accessOrder为true则表示按照基于访问的顺序来排列,意思就是最近使用的entry,放在链表的最末尾 //在取值后对参数accessOrder进行判断,如果为true,执行afterNodeAccess if (accessOrder) afterNodeAccess(e); return e.value;}//此函数执行的效果就是将最近使用的Node,放在链表的最末尾void afterNodeAccess(Node
e) { LinkedHashMap.Entry
last; //仅当按照LRU原则且e不在最末尾,才执行修改链表,将e移到链表最末尾的操作 if (accessOrder && (last = tail) != e) { //将e赋值临时节点p, b是e的前一个节点, a是e的后一个节点 LinkedHashMap.Entry
p = (LinkedHashMap.Entry
)e, b = p.before, a = p.after; //设置p的后一个节点为null,因为执行后p在链表末尾,after肯定为null p.after = null; //情况一(p为头部):p前一个节点为null if (b == null) head = a; else b.after = a; //情况二(p为尾部):p的后一个节点为null if (a != null) a.before = b; else last = b; //情况三(p为链表里的第一个节点) if (last == null) head = p; else { //正常情况,将p设置为尾节点的准备工作,p的前一个节点为原先的last,last的after为p p.before = last; last.after = p; } //将p设置为尾节点 tail = p; // 修改计数器+1 ++modCount; }}

概念:

LRU(Least Recently Used): 意思就是最近读取的数据放在最前面,最早读取的数据放在最后面,如果这个时候有新的数据进来,那么最后面存储的数据淘汰。

说明一下:

正常情况下:查询的p在链表中间,那么将p设置到末尾后,它原先的 前节点b 和 后节点a 就变成了前后节点。

情况一:p为头部,前一个节点b不存在,那么考虑到p要放到最后面,则设置p的后一个节点a为head。

情况二:p为尾部,后一个节点a不存在,那么考虑到统一操作,设置last为b。

情况三:p为链表里的第一个节点,head=p。

将最近使用的Node,放在链表的最末尾示意图:

4.2,put()方法

LinkedHashMap的put方法调用的还是HashMap里的put,不同的是重写了里面的部分方法。

LinkedHashMap将其中newNode方法以及之前设置下的钩子方法afterNodeAccess(该方法上面已说明)和afterNodeInsertion进行了重写,从而实现了加入链表的目的。

Node
newNode(int hash, K key, V value, Node
e) { LinkedHashMap.Entry
p = new LinkedHashMap.Entry
(hash, key, value, e); linkNodeLast(p); return p;}//把新加的节点放在链表的最后面。private void linkNodeLast(LinkedHashMap.Entry
p) { //将tail给临时变量last LinkedHashMap.Entry
last = tail; tail = p; //若没有last,说明p是第一个节点,head=p if (last == null) head = p; else { p.before = last; last.after = p; }}//插入后把最老的Entry删除,不过removeEldestEntry总是返回false,所以不会删除,估计又是一个钩子方法给子类用的void afterNodeInsertion(boolean evict) { LinkedHashMap.Entry
first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); }}protected boolean removeEldestEntry(Map.Entry
eldest) { return false;}

4.3,remove()方法

在HashMap的remove方法中也有一个钩子方法afterNodeRemoval。

LinkedHashMap的remove方法调用的还是HashMap里的remove,不同的是重写了里面的部分方法。

void afterNodeRemoval(Node
e) { //记录e的前后节点b,a LinkedHashMap.Entry
p = (LinkedHashMap.Entry
)e, b = p.before, a = p.after; //p已删除,前后指针都设置为null,便于GC回收 p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b;}

4.4,transferLinks()方法

//替换节点的方法,我们使用的replacementNode,replacementTreeNode等方法都是通过该方法实现的private void transferLinks(LinkedHashMap.Entry
src, LinkedHashMap.Entry
dst) { LinkedHashMap.Entry
b = dst.before = src.before; LinkedHashMap.Entry
a = dst.after = src.after; if (b == null) head = dst; else b.after = dst; if (a == null) tail = dst; else a.before = dst;}

dst节点替换src节点示意图:

5,LinkedHashMap的迭代器

abstract class LinkedHashIterator {    //记录下一个Entry    LinkedHashMap.Entry
next; //记录当前的Entry LinkedHashMap.Entry
current; //记录是否发生了迭代过程中的修改 int expectedModCount;  LinkedHashIterator() { //初始化的时候把head给next next = head; expectedModCount = modCount; current = null; }  public final boolean hasNext() { return next != null; }  final LinkedHashMap.Entry
nextNode() { LinkedHashMap.Entry
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
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; }}

 

转载于:https://www.cnblogs.com/Zender/p/8516343.html

你可能感兴趣的文章
怎么部署TFS
查看>>
六、网络数据编码与解码
查看>>
解决xp搜索“文件中的一个字或者词组”失效
查看>>
南阳理工学院动态规划专题 回文字符串
查看>>
测试管理工具列表大全
查看>>
python windows package/module 安装
查看>>
MySQLdb
查看>>
python中文输出和写入文本
查看>>
Knight Moves
查看>>
无锁栈与无锁队列
查看>>
微信开发第8章 通过accesstoken将长连接转换为短链接
查看>>
[刷题]Codeforces 785D - Anton and School - 2
查看>>
四川红油的制法
查看>>
Java重写《C经典100题》 --21
查看>>
【Android基础】Fragment 详解之Fragment生命周期
查看>>
链表(裸题)
查看>>
11运算符重载
查看>>
磁盘系统的管理
查看>>
C/S
查看>>
Http Get/Post请求的区别
查看>>