白话 LRU 缓存及链表的数据结构讲解(三)

双向链表

链表的作用就是按照访问的时间顺序,无论单链表或双链表都如此。我们在单链表的例子看到,维护单链表通常离不开从头部节点开始遍历的操作,尽管有许多巧妙的优化办法,但是只要从链表中查找某个元素(随机访问),必然还是离不开遍历操作。有鉴于此,我们希望可以常数时间内(O(1))随机访问元素,这样就很容易想到 HashMap 了,没错,就是要让 HashMap 加入进来使用。另外,有人问,直接用 HashMap 不行么?HashMap 本身无顺序,而且要定位某个元素,还是要遍历这个 Map。

至于双向链表(Double Linked List),插入、删除更简单,关键双向链表的操作基本都是 O(1) ,更快了,这里我们就毫不客气地使用了。另外,有人问,单纯用双链表不行么?也可以但改进的意义不大,查找元素还是得遍历。 关于各个选型的时间复杂度比较

  • 单链表:O(n)
  • 单链表 + HashMap:O(n) 意义不大
  • 双向链表:部分 O(1),查找元素还是得遍历 O(n)
  • 双向链表+ HashMap:O(1)
  • HashMap:访问 O(1),但遍历 O(n)
  • 队列:队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端

下一步的优化方法便是采用双向链表+ HashMap。

java 复制代码
public class LRUCache<K, V> {
  static class Node<K, V> {
		K key;
		V value;
		Node<K, V> pre;
		Node<K, V> next;

		public Node(K key, V value) {
			this.key = key;
			this.value = value;
		}
	}

	private HashMap<K, Node<K, V>> map;
	private int capicity, count;
	private Node<K, V> head, tail;

	/**
	 * 
	 * @param capacity 缓存容量
	 */
	public LRUCache(int capacity) {
		this.capicity = capacity;
		map = new HashMap<>();
		head = new Node<>(null, null);
		tail = new Node<>(null, null);

		head.next = tail;
		head.pre = null;
		tail.pre = head;
		tail.next = null;
		count = 0;
	}

	/**
	 * 加到头部
	 * 
	 * @param node
	 */
	public void addToHead(Node<K, V> node) {
		node.next = head.next;
		node.next.pre = node;
		node.pre = head;
		head.next = node;
	}

	/**
	 * 删除节点
	 * 
	 * @param node
	 */
	public void deleteNode(Node<K, V> node) {
		node.pre.next = node.next;
		node.next.pre = node.pre;
	}

	public V get(int key) {
		if (map.get(key) != null) {
			Node<K, V> node = map.get(key);
			V result = node.value;
			deleteNode(node);
			addToHead(node);
			
			return result;
		}

		return null; // 找不到
	}

	public void set(K key, V value) {
		System.out.println("Going to set the (key, value) : (" + key + ", " + value + ")");
		if (map.get(key) != null) { // 已存在
			Node<K, V> node = map.get(key);
			node.value = value;
			deleteNode(node);
			addToHead(node);
		} else {// 插入新的
			Node<K, V> node = new Node<>(key, value);
			map.put(key, node);
			
			if (count < capicity) {
				count++;
				addToHead(node);
			} else { // 满了
				map.remove(tail.pre.key);
				deleteNode(tail.pre);
				addToHead(node);
			}
		}
	}
}

掌握了前面的这些基础,再回头来看看双向链表(Double Linked List),感觉轻松简单多了。

浓缩版

就是 LinkedHashMap 啦,网上一堆介绍,笔者就不重复了。

java 复制代码
import java.util.LinkedHashMap;
	import java.util.Map;
	
	/**
	 * Created by liuzhao on 14-5-15.
	 */
	public class LRUCache2<K, V> extends LinkedHashMap<K, V> {
	    private final int MAX_CACHE_SIZE;
	
	    public LRUCache2(int cacheSize) {
	        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
	        MAX_CACHE_SIZE = cacheSize;
	    }
	
	    @Override
	    protected boolean removeEldestEntry(Map.Entry eldest) {
	        return size() > MAX_CACHE_SIZE;
	    }
	
	    @Override
	    public String toString() {
	        StringBuilder sb = new StringBuilder();
	        for (Map.Entry<K, V> entry : entrySet()) {
	            sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue()));
	        }
	        return sb.toString();
	    }
	}

带锁的线程安全的 LRULinkedHashMap 简单实现 blog.csdn.net/a921122/art...

参考文献

www.cnblogs.com/dolphin0520... my.oschina.net/zjllovecode... www.jianshu.com/p/b1ab4a170... www.sohu.com/a/298778364... www.geeksforgeeks.org/design-a-da... blog.csdn.net/qq_34417408... 分离链表结构和 lru

相关推荐
神奇的程序员3 小时前
从已损坏的备份中拯救数据
运维·后端·前端工程化
oden4 小时前
AI服务商切换太麻烦?一个AI Gateway搞定监控、缓存和故障转移(成本降40%)
后端·openai·api
李慕婉学姐5 小时前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
m0_740043735 小时前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
招风的黑耳6 小时前
我用SpringBoot撸了一个智慧水务监控平台
java·spring boot·后端
Miss_Chenzr6 小时前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
期待のcode6 小时前
Springboot核心构建插件
java·spring boot·后端
2501_921649496 小时前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
serendipity_hky7 小时前
【SpringCloud | 第5篇】Seata分布式事务
分布式·后端·spring·spring cloud·seata·openfeign
五阿哥永琪7 小时前
Spring Boot 中自定义线程池的正确使用姿势:定义、注入与最佳实践
spring boot·后端·python