LinkedHashMap&LinkedHashSet源码解析
LinkedHashMap
概述
LinkedHashSet使用适配器模式包装了LinkedHashSet
一个有序的散列表,允许key为null也允许value为空,从名字上可以看出使用链表维护了有序性
在元素存储时,在原来的HashMap的数组+链表的基础上,为每个元素添加了pre和next指针,构成了一个双向链表
注意:内部没有使用红黑树
同样的内部有多个参数
-
head:虚拟头结点
-
tail:虚拟尾节点
-
初始容量:16
-
负载因子:扩容临界值
插入
-
插入到对应的bucket的单链表中
-
插入到双向链表的尾部
删除
-
删除对应bucket中的元素
-
双向链表中断链
缓存用法
LinkedHashMap有一个子类方法protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
,该方法的作用是告诉Map是否要删除“最老”的Entry,所谓最老就是当前Map中最早插入的Entry,如果该方法返回true
,最老的那个元素就会被删除。
重写这个子方法可以实现一个LRU缓存
LRU缓存
使用LinkedHashMap实现一个指定容量的LRU缓存
public class LRUCache extends LinkedHashMap<Integer,Integer> { private int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity = capacity; } public int get(int key) { return super.getOrDefault(key, -1); } public void put(int key, int value) { super.put(key, value); } @Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return size() > capacity; } }
概述:只需要继承LinkedHashMap类,使用构造方法指定容量、负载因子、顺序规则(true访问顺序,false插入顺序),然后重写removeEldestEntry方法即可,在超过指定大小时返回true
如何手写一个LinkedHashMap?
手写LinkedHashMap一般指手动实现一个LRU缓存
其中包含四个部分
-
自定义Node节点:需要使用双向链表
-
自定义一个内部map:需要使用散列表
-
初始化capacity、size、head、tail,其中头尾是虚拟节点
-
实现四个方法
-
moveToHead:移动到头部
-
addToHead:添加到头部
-
removeEldestNode:删除尾巴节点
-
removeNode:删除节点---断链
-
插入节点
创建Node,添加进map中,添加到链表头部,超容量就删除尾部节点,删除的时候调用removeNode断链,然后在map中也删除
查询节点
查询map中你的Node,移动到头部(先断链,再添加)
实现代码
public class MyLRUCache { static class Node { int key; int value; Node pre; Node next; public Node(){} public Node(int key, int value) { this.key = key; this.value = value; this.pre = null; this.next = null; } } private Map<Integer, Node> cache = new HashMap<>(); private int size; private int capacity; private Node head, tail; public MyLRUCache(int capacity) { this.size = 0; this.capacity = capacity; head = new Node(); tail = new Node(); head.next = tail; tail.pre = head; } private void moveToHead(Node node){ //移动到头部 removeNode(node); addToHead(node); } private void addToHead(Node node){ //添加到头部 node.next = head.next; node.pre = head; head.next.pre = node; head.next = node; } private void removeEldestNode(){ //删除尾巴节点 Node temp = tail.pre; removeNode(temp); this.cache.remove(temp.key); } private void removeNode(Node node) { //删除节点 node.pre.next = node.next; node.next.pre = node.pre; } public int get(int key) { Node node = this.cache.get(key); if(node == null) return -1; //移动到头部 moveToHead(node); return node.value; } public void put(int key, int value) { Node node = this.cache.get(key); if(node == null) { //不存在就创建,添加进哈希表 node = new Node(key, value); cache.put(key, node); //添加进头部 addToHead(node); size++; if(size > capacity){ //删除尾巴节点 removeEldestNode(); size--; } }else { node.value = value; moveToHead(node); } } }
总结
使用HashMap作为存储容器,容器中不是存储元素自身,而是存储Node节点,并且Node节点互相连接形成了一个双向链表
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-08-24 【Java数据结构】循环队列的数组实现
2020-08-24 【Java数据结构】普通矩阵与稀疏矩阵的互相转化,稀疏矩阵的物理存储