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年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
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)]