最新【JDK源码】集合之LinkedHashMap,浦发银行java开发面试

面试结束复盘查漏补缺

每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。

以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~

重要的事说三遍,关注+关注+关注!

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

image.png

更多笔记分享

历经30天,说说我的支付宝4面+美团4面+拼多多四面,侥幸全获Offer

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

【JDK源码】LinkedList

继承体系

在这里插入图片描述

  • LinkedHashMap继承HashMap,拥有HashMap的所有特性,并且额外增加了按一定顺序访问的特性。

LinkedHashMap是怎样保证顺序的呢?

在这里插入图片描述

  • 通过上面的继承体系,我们知道它继承了HashMap,所以它的内部也有(数组+链表+红黑树)这三种结构,但是它还额外添加了一种“双向链表”的结构存储所有元素的顺序。

添加删除元素的时候需要同时维护在HashMap中的存储,也要维护在LinkedList中的存储,所以性能上来说会比HashMap稍慢。

基本属性

/**

  • 双向链表头节点

*/

transient LinkedHashMap.Entry<K,V> head;

/**

  • 双向链表尾节点

*/

transient LinkedHashMap.Entry<K,V> tail;

/**

  • 是否按访问顺序排序

  • true : 按照访问顺序排序

  • false: 按照插入顺序排序

*/

final boolean accessOrder;

(1)head

双向链表的头节点,旧数据存在头节点。

(2)tail

双向链表的尾节点,新数据存在尾节点。

(3)accessOrder

是否需要按访问顺序排序,通过注释发现该变量为true时access-order,即按访问顺序遍历,此时你任何一次的操作,包括put、get操作,都会改变map中已有的存储顺序,如果为false,则表示按插入顺序遍历。默认为false 也就是按照插入顺序

构造方法

/**

  • 构造方法1,构造一个指定初始容量和负载因子的、按照插入顺序的LinkedList

*/

public LinkedHashMap(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor);

accessOrder = false;

}

/**

  • 构造方法2,构造一个指定初始容量的LinkedHashMap,取得键值对的顺序是插入顺序

*/

public LinkedHashMap(int initialCapacity) {

super(initialCapacity);

accessOrder = false;

}

/**

  • 构造方法3,用默认的初始化容量和负载因子创建一个LinkedHashMap,取得键值对的顺序是插入顺序

*/

public LinkedHashMap() {

super(); //其实就是调用的hashmap的默认构造方法,默认加载因子0.75

accessOrder = false;

}

/**

  • 构造方法4,通过传入的map创建一个LinkedHashMap,容量为默认容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,加

*载因子为默认值

*/

public LinkedHashMap(Map<? extends K, ? extends V> m) {

super(m);

accessOrder = false;

}

/**

  • 构造方法5,根据指定容量、加载因子和键值对保持顺序创建一个LinkedHashMap

*/

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {

super(initialCapacity, loadFactor);

this.accessOrder = accessOrder;

}

  • 前四个构造方法accessOrder都等于false,说明双向链表是按插入顺序存储元素。

  • 最后一个构造方法accessOrder从构造方法参数传入,如果传入true,则就实现了按访问顺序存储元素,这也是实现LRU缓存策略的关键。

内部类

// 位于LinkedHashMap中

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);

}

}

// 位于HashMap中

static class Node<K,V> implements Map.Entry<K,V> {

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()

最后

面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典

  • Java核心知识整理

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Java核心知识

  • Spring全家桶(实战系列)

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

  • 其他电子书资料

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

全宝典**

  • Java核心知识整理

[外链图片转存中…(img-O4XOoVz9-1715654823219)]

Java核心知识

  • Spring全家桶(实战系列)

[外链图片转存中…(img-1IV2CpOF-1715654823220)]

  • 其他电子书资料

[外链图片转存中…(img-Pqkx8o6M-1715654823220)]

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

[外链图片转存中…(img-FmGcWYmd-1715654823220)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值