【JDK源码】集合之LinkedHashMap,2024年最新编写asp程序

final int hash;

final K key;

V value;

Node<K,V> next;

Node(int hash, K key, V value, Node<K,V> next) {

this.hash = hash;

this.key = key;

this.value = value;

this.next = next;

}

}

存储节点,继承自HashMap的Node类,next用于单链表存储于桶中,before和after用于双向链表存储所有元素。

newNode()

LinkedHashMap重写了newNode()方法,通过此方法保证了插入的顺序性,在此之前我们先看一下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);//其实还是用的hashmap中的方法

linkNodeLast§;

return p;

}

这里调用了一个方法 linkNodeLast(),我们看一下这个方法,但是这和方法不止完成了串联后置,也完成了串联前置,所以插入的顺序性是通过这个方法保证的。

// link at the end of list 链接当前结点和尾结点

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {

//保存尾结点引用

LinkedHashMap.Entry<K,V> last = tail;

tail = p;

//如果这条链表为空。则直接设置为头结点

if (last == null)

head = p;

else {

//将p结点与尾结点相连通

p.before = last;

last.after = p;

}

}

  • 插入的顺序的保证是通过newNode()方法再调用linkNodeLast()

  • 将新插入的结点连接到尾结点

afterNodeAccess()

//也就是把p结点从中间拿出来放到尾部

void afterNodeAccess(Node<K,V> e) { // move node to last 将节点移动到最后一个

LinkedHashMap.Entry<K,V> last;

// accessOrder 确定是按照访问顺序的,如果当前节点不是最后节点,因为是的话就不用移了

if (accessOrder && (last = tail) != e) {

//强转一下e给到p。然后把e的前后结点都取出来

LinkedHashMap.Entry<K,V> p =

(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;

//因为要插到最后所以先给null

p.after = null;

//如果p结点前结点为空,将p的后结点变头结点

if (b == null)

head = a;

else

//将p的前结点指向p的后结点 b>p>a b>a

b.after = a;

//将a也指向b a<>b

if (a != null)

a.before = b;

else

//a等于null,就将b作为尾结点

last = b;

if (last == null)

head = p;

else {

// last<>p

p.before = last;

last.after = p;

}

//尾结点p

tail = p;

++modCount;

}

}

  • 在HashMap中没给具体实现,而在LinkedHashMap重写了,目的是保证操作过的Node节点永远在最后,从而保证读取的顺序性,在调用put方法和get方法时都会用到

  • 从双向链表中移除访问的节点,把访问的节点加到双向链表的末尾;(末尾为最新访问的元素)

newNode()方法中调用的 linkNodeLast(Entry e)方法和现在的afterNodeAccess(Node e)都是将传入的Node节点放到最后,那么它们的使用场景如何呢?

HashMap的put流程中,如果在对应的hash位置上还没有元素,那么直接new Node()放到数组table中,这个时候对应到LinkedHashMap中,调用了newNode()方法,就会用到linkNodeLast(),将新node放到最后

如果对应的hash位置上有元素,进行元素值的覆盖时,就会调用afterNodeAccess(),将原本可能不是最后的node节点移动到了最后删减之后的put代码逻辑

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {

if ((p = tab[i = (n - 1) & hash]) == null)

// 调用linkNodeLast方法将新元素放到最后

tab[i] = newNode(hash, key, value, null);

else {

if (e != null) {

// 如果key已经存在

V oldValue = e.value;

if (!onlyIfAbsent || oldValue == null)

e.value = value;

// 将存在的节点放到最后(因为存在所以是Access,也就是访问存在的元素)

afterNodeAccess(e);

return oldValue;

}

}

++modCount;

if (++size > threshold)

resize();

afterNodeInsertion(evict);

return null;

}

get代码逻辑

public V get(Object key) {

Node<K,V> e;

if ((e = getNode(hash(key), key)) == null)

return null;

if (accessOrder)

afterNodeAccess(e); // 如果是采用访问顺序遍历就会将get的key放到尾部

return e.value;

}

下面给出一个例子

//这里是按照访问顺序排序的

final HashMap<Integer, Integer> m = new LinkedHashMap<Integer, Integer>(16, 0.75f,true);

m.put(3,1);

m.put(4,1);

m.put(1,1);

m.put(2,1);

m.put(8,1);

m.get(3);//也会调用到afterNodeAccess

m.put(2,1); //重新put就会调用afterNodeAccess()将key=2的结点放到尾部

for (Map.Entry<Integer, Integer> key : m.entrySet()) {

System.out.print(key.getKey()+“\t”);

}

//4 1 8 3 2

afterNodeInsertion()

void afterNodeInsertion(boolean evict) { // possibly remove eldest

LinkedHashMap.Entry<K,V> first;

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;

}

  • 如果evict为true,且头节点不为空,且确定移除最老的元素,那么就调用HashMap.removeNode()把头节点移除(这里的头节点是双向链表的头节点,而不是某个桶中的第一个元素)

  • 关于afterNodeAccess()方法,在HashMap中依然没给具体实现,LinkedHashMap中还重写了afterNodeInsertion(boolean evict)方法,它的目的是移除链表中最老的节点对象也就是当前在头部的节点对象,但实际上在JDK8中不会执行,因为removeEldestEntry方法始终返回false

afterNodeInsertion 方法的意义是什么呢

因为removeEldestEntry 固定返回false , 那这个方法的意义是什么呢 ?·

afterNodeInsertion方法的evict参数如果为false,表示哈希表处于创建模式。只有在使用Map集合作为构造器参数创建LinkedHashMap或HashMap时才会为false,使用其他构造器创建的LinkedHashMap,之后再调用put方法,该参数均为true。

下面给出了单独put 的情况

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

/**

  • @param evict if false, the table is in creation mode.

  • @return previous value, or null if none

*/

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)

这里是使用Map集合作为构造器参数创建的时的情况

在这里插入图片描述

HashMap 中也有类似的操作,就是这三个方法

// Callbacks to allow LinkedHashMap post-actions

void afterNodeAccess(Node<K,V> p) { }

void afterNodeInsertion(boolean evict) { }

void afterNodeRemoval(Node<K,V> p) { }

很明显这三个方法,都在LinkedHashMap 中被重写了,所以下面的方法是因为是有返回值的,所以它不在是空方法体了,而是一个直接返回false 的方法体了

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {

return false;

}

所以这里的意义就是让你去实现,为什么这么做呢?LinkedHashMap 就是用在记录顺序的场景下,一个典型应用就是LRU,也就是主要用在缓存上面,因为此时的设计虽然保留了LRU的基本特性,但是整个链表的大小是没有限制的大小没有限制的缓存,后面肯定就是无限的GC了,因为这里都是强引用

实现一个大小确定的LRU

(LinkedHashMap)

如果一个链表只能维持10个元素,那么当插入了第11个元素时,以如下方式重写removeEldestEntry的话,那么将会删除最老的一个元素

public class LiuLinkedHashMap<K, V> extends LinkedHashMap<K, V> {

private int maxCacheSiz;

public LiuLinkedHashMap(int maxCacheSize) {

super();

this.maxCacheSiz = maxCacheSize;

}

@Override

protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {

if (size() > maxCacheSiz) {

System.out.println(“即将执行删除”);

return true;

} else {

return false;

}

}

public static void main(String[] args) {

LiuLinkedHashMap<Integer, Integer> liuLinkedHashMap = new LiuLinkedHashMap(10);

LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap(10);

for (int i = 0; i < 11; i++) {

LiuLinkedHashMap.put(i, i);

linkedHashMap.put(i, i);

}

System.out.println(“我的map的大小:”+buerLinkedHashMap.size());

System.out.println(“默认map的大小:”+linkedHashMap.size());

}

}

===============================================

即将执行删除

我的map的大小:10

默认map的大小:11

afterNodeRemoval ()

//就是将e从双链表中移除

void afterNodeRemoval(Node<K,V> e) { // unlink

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
img
img
img

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
img

emoval(Node<K,V> e) { // unlink

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-6hv3xn7l-1712543297825)]
[外链图片转存中…(img-c2LtN6AS-1712543297826)]
[外链图片转存中…(img-GQD4Bh7K-1712543297826)]

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-uCt6yoL4-1712543297826)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值