LRU算法实现
JDK中的实现
在JDK中LinkedHashMap可以作为LRU算法以及插入顺序的实现,LinkedHashMap继承自HashMap,底层结合hash表和双向链表,元素的插入和查询等操作通过计算hash值找到其数组位置,在做插入或则查询操作是,将元素插入到链表的表头(当然得先删除链表中的老元素),如果容量满了,则删除LRU这个元素,在链表表尾的元素即是。
LinkedHashMap的时间复杂度和HashMap差不多,双向链表的删除和表头插入等操作都是O(1)复杂度,故不会影响HashMap的操作性能,插入,查询,删除LRU元素等操作均是O(1)的时间复杂度。
LinkedHashMap不是线程安全的类,用于多线程缓存则需要花点心思去同步它了,JDK中有支持并发的高性能ConcurrenHashMap,没有ConcurrenListHashMap的实现,故要使用支持并发的高性能LRU算法还得靠自己去继承ConcurrenHashMap或则其他方式实现。Google以及其它资深研发团队已有ConcurrenListHashMap的实现
当然知道了原理,我们自己也可以来实现LRU算法
1.简单的计数或则LU时间标记法
底层使用hash表,插入和访问的时间都可以做到O(1)复杂度,容量满了需要删除LRU,则需要遍历一遍数组,通过计数或则LU时间得到LRU,删除之,时间复杂度O(n)。此算法简单容易实现,适合数据量不大的需求
2.通过栈或双向链表
这种算法通过维护元素的在链表中的顺序来达到计算元素的访问热度,不需要额外的空间来计数或则记录访问时间。
插入时,先检查改元素存在此链表中没有,有的删除之,然后再将元素插入表头。
访问时,遍历数组查找该元素,然后将该元素移动至表头。
这样一来,最近被访问的元素都在表头,故要找出LRU则只需要删除表尾元素即可。插入和访问的时间复杂度均为O(n),如果用来作为缓存(查询操作频繁),相比hash表的实现,性能相当不好啊
3.结合HashMap和双向链表
通过上面可知道HashMap可实现读写O(1)复杂度,但是找出LRU需要遍历整个数组,而通过维护链表则相反,仅需要O(1)就可以找出LRU,故将两者结合起来,实现综合复杂度为O(1)的LRU算法
使用数组来存放元素,插入时通过hash计算出其位置,然后改变该元素在链表中的指针,两个操作的时间复杂度均为1。具体实现可参考JDK的LinkedHashMap