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节点互相连接形成了一个双向链表

posted @   枫叶藏在眼眸  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2020-08-24 【Java数据结构】循环队列的数组实现
2020-08-24 【Java数据结构】普通矩阵与稀疏矩阵的互相转化,稀疏矩阵的物理存储
点击右上角即可分享
微信分享提示