手把手教你实现LRU算法(Java版)

一、前言

LRU,全称Least Recently Used,即最近最少使用算法,怎么理解?就是使用一个有序固定容量大小的队列维持一堆数据,当往队列插入一个不存在的数据时,就会淘汰掉最长时间没有使用的数据,我们把这个算法成为LRU算法。

LRU在日常开发中非常常见,而缓存机制就是使用LRU的最佳案例。

二、LRU算法实现

LRU应该支持以下操作: 获取数据 get 和 写入数据 put 。

  • 获取数据 get(key):如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
  • 写入数据 put(key, value):如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

按照上面的说法,我们只要使用一个数据或者一个链表就能实现该功能,但是你会发现这样实现的LRU时间复杂度会非常高,所以作为有追求的程序员,我们应该实现一个复杂度相对比较好的算法。

1.使用链表和哈希表实现LRU

首先我们需要使用链表提供一个固定大小、并且是能够实现有序排列功能的队列,为什么不用数组?因为数组对于移动数据的时间复杂度太高了,所以不考虑使用数组。

其次我们需要使用哈希表来实现高效的get、put操作,它的平均时间复杂度能达到O(1)。

这种方法在时间复杂度上还是没有达到最佳,因为把一个元素从链表中删除,它的时间复杂度是O(n)。在Java中具体实现就可以使用LinkedList和HashMap来实现,具体代码实现如下:

// 时间复杂度:get达到O(1),set方法O(n)
// 空间复杂度:O(capacity)
class LRUCache {
    private LinkedList<Integer> mQueue;
    private HashMap<Integer, Integer> mMap;
    private int mCapacity;
    public LRUCache(int capacity) {
        this.mCapacity = capacity;
        mQueue = new LinkedList<Integer>();
        mMap = new HashMap<Integer, Integer>();
    }
    
    public int get(int key) {
        Integer result = mMap.get(key);
        if(result == null) {
            return -1;
        } else {
            // 当发现队列里面存在数据时,我们需要把它从队列里面移动到队尾
            mQueue.remove((Integer)key);
            mQueue.offer(key);
            return result;
        }
    }
    
    public void put(int key, int value) {
        Integer result = mMap.get(key);
        if(result != null) {
            // 当发现队列里面存在该数据时
            // 则把它移动到队尾
            mQueue.remove((Integer)key);
            mQueue.offer(key);
            mMap.put(key, value);
        } else {
            // 当发现队列里面不存在数据时
            if(mQueue.size() >= mCapacity) {
                // 如果队列大小超过了容量值,就需要把队头元素删掉
                int head = mQueue.poll();
                // 并且从hashMap里面抹掉
                mMap.remove(head);
            }
            mQueue.offer(key);
            mMap.put(key, value);
        }
    }
}

2.使用双向链表和哈希表来实现LRU

在第一种实现方案中,我们在put方法中的时间复杂度为O(n),那么有没有办法让这里的时间复杂度达到O(1)呢?答案是有的,怎么实现呢?就是使用一个双向链表加哈希表来实现,如下图所示。其实在Java中有一种数据结构的底层实现正是如此,即LinkedHashMap,它提供了实现有序的HashMap。

image

例如我们哈希表里面有三个元素,分别是Node A,Node B,Node C,但是它们之间是无序的,为了让它们有序,我们通过在它们存储的值里面定义前后指针,从而形成了一个有序的哈希表,这样就能实现get、put都为O(1)的LRU算法,具体实现代码如下:

// 时间复杂度:get、set方法都达到O(1)
// 空间复杂度:O(capacity)
class LRUCache {
	class Node {
		int key;
		int value;
		Node pre;
		Node next;
		
		Node(int key, int value, Node pre, Node next) {
			this.key = key;
			this.value = value;
			this.pre = pre;
			this.next = next;
		}
	}

	private HashMap<Integer, Node> mMap;
	private int mCapacity;
	private Node mHead;
	private Node mTail;

	public LRUCache(int capacity) {
		this.mCapacity = capacity;
		mMap = new HashMap<>();
	}

	public int get(int key) {
		Node node = mMap.get(key);
		if (node == null) {
			return -1;
		} else {
			moveToTail(node);
			return node.value;
		}
	}

	public void put(int key, int value) {
		Node result = mMap.get(key);
		Node newNode = new Node(key, value, null, null);
		if (result == null) {
			// 队列中不存在该元素
			if (mMap.size() < mCapacity) {
				// 队列没有满
				if (mMap.size() == 0) {
					// 直接插到队头
					mHead = newNode;
					mTail = newNode;
					mMap.put(key, newNode);
				} else {
					// 直接插到队尾
					mTail.next = newNode;
					newNode.pre = mTail;
					mTail = newNode;
					mMap.put(key, newNode);
				}
			} else {
				// 队列已经满了
				Node oldNode = removeHead();
				mMap.remove(oldNode.key);
				
				addLast(newNode);
				mMap.put(key, newNode);
			}
		} else {
			result.value = value;
			// 队列中存在该元素
			moveToTail(result);
		}
	}

	private void moveToTail(Node node) {
		if (mTail != node) {
			Node pre = node.pre;
			Node next = node.next;
			if (pre != null) {
				pre.next = next;
			}
			if (next != null) {
				next.pre = pre;
				if (next.pre == null) {
					mHead = next;
				}
			}
			if (mTail != null) {
				mTail.next = node;
				node.next = null;
				node.pre = mTail;
			}
			mTail = node;
		}
	}
	
	private void addLast(Node newNode) {
		if (mTail == null) {
			mHead = newNode;
		} else {
			mTail.next = newNode;
			newNode.pre = mTail;
		}
		mTail = newNode;
	}

	private Node removeHead() {
		Node deleteNode = mHead;
		if (mHead != null) {
			Node next = mHead.next;
			if (next != null) {
				next.pre = null;
			} else {
				mTail = null;
			}
			mHead = next;
		}
		return deleteNode;
	}

}
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值