Java面试官问我懂不懂LinkedHashMap,我一口气说了五分钟(1)

关于双向链表,同学们可以回头看一遍我写的 LinkedList 那篇文章,会对理解本篇的 LinkedHashMap 有很大的帮助。

在继续下面的内容之前,我先贴一张图片,给大家增添一点乐趣——看我这心操的。 UUID 那篇文章的标题里用了“可笑”和“你”,结果就看到了下面这么乐呵的留言。

(到底是知道还是不知道,我搞不清楚了。。。)那 LinkedHashMap 这篇也用了“你”和“可笑”,不知道到时候会不会有人继续对号入座啊,想想就觉得特别欢乐。

01、插入顺序

HashMap 那篇文章里,我有讲解到一点,不知道同学们记不记得,就是 null 会插入到 HashMap 的第一位。

Map<String, String> hashMap = new HashMap<>();

hashMap.put(“沉”, “沉默王二”);

hashMap.put(“默”, “沉默王二”);

hashMap.put(“王”, “沉默王二”);

hashMap.put(“二”, “沉默王二”);

hashMap.put(null, null);

for (String key : hashMap.keySet()) {

System.out.println(key + " : " + hashMap.get(key));

}

输出的结果是:

null : null

默 : 沉默王二

沉 : 沉默王二

王 : 沉默王二

二 : 沉默王二

虽然 null 最后一位 put 进去的,但在遍历输出的时候,跑到了第一位。

那再来对比看一下 LinkedHashMap。

Map<String, String> linkedHashMap = new LinkedHashMap<>();

linkedHashMap.put(“沉”, “沉默王二”);

linkedHashMap.put(“默”, “沉默王二”);

linkedHashMap.put(“王”, “沉默王二”);

linkedHashMap.put(“二”, “沉默王二”);

linkedHashMap.put(null, null);

for (String key : linkedHashMap.keySet()) {

System.out.println(key + " : " + linkedHashMap.get(key));

}

输出结果是:

沉 : 沉默王二

默 : 沉默王二

王 : 沉默王二

二 : 沉默王二

null : null

null 在最后一位插入,在最后一位输出。

输出结果可以再次证明,HashMap 是无序的,LinkedHashMap 是可以维持插入顺序的。

那 LinkedHashMap 是如何做到这一点呢?我相信同学们和我一样,非常希望知道原因。

要想搞清楚,就需要深入研究一下 LinkedHashMap 的源码。LinkedHashMap 并未重写 HashMap 的 put() 方法,而是重写了 put() 方法需要调用的内部方法 newNode()

HashMap.Node<K,V> newNode(int hash, K key, V value, HashMap.Node<K,V> e) {

LinkedHashMap.Entry<K,V> p =

new LinkedHashMap.Entry<>(hash, key, value, e);

linkNodeLast§;

return p;

}

前面说了,LinkedHashMap.Entry 继承了 HashMap.Node,并且追加了两个字段 before 和 after。

那,紧接着来看看 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;

}

}

看到了吧,LinkedHashMap 在添加第一个元素的时候,会把 head 赋值为第一个元素,等到第二个元素添加进来的时候,会把第二个元素的 before 赋值为第一个元素,第一个元素的 afer 赋值为第二个元素。

这就保证了键值对是按照插入顺序排列的,明白了吧?

注:我用到的 JDK 版本为 14

02、访问顺序

LinkedHashMap 不仅能够维持插入顺序,还能够维持访问顺序。访问包括调用 get() 方法、remove() 方法和 put() 方法。

要维护访问顺序,需要我们在声明 LinkedHashMap 的时候指定三个参数。

LinkedHashMap<String, String> map = new LinkedHashMap<>(16, .75f, true);

第一个参数和第二个参数,看过 HashMap 的同学们应该很熟悉了,指的是初始容量和负载因子。

第三个参数如果为 true 的话,就表示 LinkedHashMap 要维护访问顺序;否则,维护插入顺序。默认是 false。

Map<String, String> linkedHashMap = new LinkedHashMap<>(16, .75f, true);

linkedHashMap.put(“沉”, “沉默王二”);

linkedHashMap.put(“默”, “沉默王二”);

linkedHashMap.put(“王”, “沉默王二”);

linkedHashMap.put(“二”, “沉默王二”);

System.out.println(linkedHashMap);

linkedHashMap.get(“默”);

System.out.println(linkedHashMap);

linkedHashMap.get(“王”);

System.out.println(linkedHashMap);

输出的结果如下所示:

{沉=沉默王二, 默=沉默王二, 王=沉默王二, 二=沉默王二}

{沉=沉默王二, 王=沉默王二, 二=沉默王二, 默=沉默王二}

{沉=沉默王二, 二=沉默王二, 默=沉默王二, 王=沉默王二}

当我们使用 get() 方法访问键位“默”的元素后,输出结果中,默=沉默王二 在最后;当我们访问键位“王”的元素后,输出结果中,王=沉默王二 在最后,默=沉默王二 在倒数第二位。

也就是说,最不经常访问的放在头部,这就有意思了。有意思在哪呢?

我们可以使用 LinkedHashMap 来实现 LRU 缓存,LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。

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

private static final int MAX_ENTRIES = 5;

public MyLinkedHashMap(

int initialCapacity, float loadFactor, boolean accessOrder) {

super(initialCapacity, loadFactor, accessOrder);

}

@Override

protected boolean removeEldestEntry(Map.Entry eldest) {

return size() > MAX_ENTRIES;

}

}

MyLinkedHashMap 是一个自定义类,它继承了 LinkedHashMap,并且重写了 removeEldestEntry() 方法——使 Map 最多可容纳 5 个元素,超出后就淘汰。

我们来测试一下。

MyLinkedHashMap<String,String> map = new MyLinkedHashMap<>(16,0.75f,true);

map.put(“沉”, “沉默王二”);

map.put(“默”, “沉默王二”);

map.put(“王”, “沉默王二”);

map.put(“二”, “沉默王二”);

map.put(“一枚有趣的程序员”, “一枚有趣的程序员”);

System.out.println(map);

map.put(“一枚有颜值的程序员”, “一枚有颜值的程序员”);

System.out.println(map);

map.put(“一枚有才华的程序员”,“一枚有才华的程序员”);

System.out.println(map);

输出结果如下所示:

{沉=沉默王二, 默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序员=一枚有趣的程序员}

{默=沉默王二, 王=沉默王二, 二=沉默王二, 一枚有趣的程序员=一枚有趣的程序员, 一枚有颜值的程序员=一枚有颜值的程序员}

{王=沉默王二, 二=沉默王二, 一枚有趣的程序员=一枚有趣的程序员, 一枚有颜值的程序员=一枚有颜值的程序员, 一枚有才华的程序员=一枚有才华的程序员}

沉=沉默王二默=沉默王二 依次被淘汰出局。

假如在 put “一枚有才华的程序员”之前 get 了键位为“默”的元素:

MyLinkedHashMap<String,String> map = new MyLinkedHashMap<>(16,0.75f,true);

map.put(“沉”, “沉默王二”);

map.put(“默”, “沉默王二”);

map.put(“王”, “沉默王二”);

map.put(“二”, “沉默王二”);

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

面试真题

Spring源码笔记

put “一枚有才华的程序员”之前 get 了键位为“默”的元素:

MyLinkedHashMap<String,String> map = new MyLinkedHashMap<>(16,0.75f,true);

map.put(“沉”, “沉默王二”);

map.put(“默”, “沉默王二”);

map.put(“王”, “沉默王二”);

map.put(“二”, “沉默王二”);

总结

在这里,由于面试中MySQL问的比较多,因此也就在此以MySQL为例为大家总结分享。但是你要学习的往往不止这一点,还有一些主流框架的使用,Spring源码的学习,Mybatis源码的学习等等都是需要掌握的,我也把这些知识点都整理起来了

[外链图片转存中…(img-OoTlDjwZ-1714398542091)]

[外链图片转存中…(img-TUkqon2X-1714398542092)]

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

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值