个人觉得LinkedHashMap 存在的意义就是为了实现 LRU 算法。

public class LinkedHashMap<K,V> extends HashMap<K,V>
implements Map<K,V>
{
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
....

1、LinkedHashMap 的 <K,V>用HashMap存储。

2、LinkedHashMap 的Key 用双向链表维护。

  当用get 和 set 方法的时候,内部维护key的双向链表的结构顺序会变动。

3、accessOrder:false 基于插入顺序  true  基于访问顺序(get一个元素后,这个元素被加到最后,使用了LRU  最近最少被使用的调度算法)。

4、removeEldestEntry方法,考虑清楚是否要重载。如果最大容量固定,则需要重载,否则表现为自适应。

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}

最简单的LRU算法实现

update1:第二个实现,读操作不必要采用独占锁,缓存显然是读多于写,读的时候一开始用独占锁是考虑到要递增计数和更新时间戳要加锁,不过这两个变量都是采用原子变量,因此也不必采用独占锁,修改为读写锁。
update2:一个错误,老是写错关键字啊,LRUCache的maxCapacity应该声明为volatile,而不是transient。
   
   最简单的LRU算法实现,就是利用jdk的LinkedHashMap,覆写其中的removeEldestEntry(Map.Entry)方法即可,如下所示:

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Map; /**
* 类说明:利用LinkedHashMap实现简单的缓存, 必须实现removeEldestEntry方法,具体参见JDK文档
*
* @author dennis
*
* @param <K>
* @param <V>
*/
public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private final int maxCapacity; private static final float DEFAULT_LOAD_FACTOR = 0.75f; private final Lock lock = new ReentrantLock(); public LRULinkedHashMap(int maxCapacity) {
super(maxCapacity, DEFAULT_LOAD_FACTOR, true);
this.maxCapacity = maxCapacity;
} @Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
return size() > maxCapacity;
}
@Override
public boolean containsKey(Object key) {
try {
lock.lock();
return super.containsKey(key);
} finally {
lock.unlock();
}
} @Override
public V get(Object key) {
try {
lock.lock();
return super.get(key);
} finally {
lock.unlock();
}
} @Override
public V put(K key, V value) {
try {
lock.lock();
return super.put(key, value);
} finally {
lock.unlock();
}
} public int size() {
try {
lock.lock();
return super.size();
} finally {
lock.unlock();
}
} public void clear() {
try {
lock.lock();
super.clear();
} finally {
lock.unlock();
}
} public Collection<Map.Entry<K, V>> getAll() {
try {
lock.lock();
return new ArrayList<Map.Entry<K, V>>(super.entrySet());
} finally {
lock.unlock();
}
}
}

如果你去看LinkedHashMap的源码可知,LRU算法是通过双向链表来实现,当某个位置被命中,通过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,如此一来,最近被命中的内容就向链表头移动,需要替换时,链表最后的位置就是最近最少使用的位置。
    LRU算法还可以通过计数来实现,缓存存储的位置附带一个计数器,当命中时将计数器加1,替换时就查找计数最小的位置并替换,结合访问时间戳来实现。这种算法比较适合缓存数据量较小的场景,显然,遍历查找计数最小位置的时间复杂度为O(n)。我实现了一个,结合了访问时间戳,当最小计数大于MINI_ACESS时(这个参数的调整对命中率有较大影响),就移除最久没有被访问的项:

package net.rubyeye.codelib.util.concurrency.cache;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
*
* @author dennis 类说明:当缓存数目不多时,才用缓存计数的传统LRU算法
* @param <K>
* @param <V>
*/
public class LRUCache<K, V> implements Serializable { private static final int DEFAULT_CAPACITY = 100; protected Map<K, ValueEntry> map; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); private final volatile int maxCapacity; //保持可见性 public static int MINI_ACCESS = 5; public LRUCache() {
this(DEFAULT_CAPACITY);
} public LRUCache(int capacity) {
if (capacity <= 0)
throw new RuntimeException("缓存容量不得小于0");
this.maxCapacity = capacity;
this.map = new HashMap<K, ValueEntry>(maxCapacity);
} public boolean ContainsKey(K key) {
try {
readLock.lock();
return this.map.containsKey(key);
} finally {
readLock.unlock();
}
} public V put(K key, V value) {
try {
writeLock.lock();
if ((map.size() > maxCapacity - 1) && !map.containsKey(key)) {
// System.out.println("开始");
Set<Map.Entry<K, ValueEntry>> entries = this.map.entrySet();
removeRencentlyLeastAccess(entries);
}
ValueEntry new_value = new ValueEntry(value);
ValueEntry old_value = map.put(key, new_value);
if (old_value != null) {
new_value.count = old_value.count;
return old_value.value;
} else
return null;
} finally {
writeLock.unlock();
}
} /**
* 移除最近最少访问
*/
protected void removeRencentlyLeastAccess(
Set<Map.Entry<K, ValueEntry>> entries) {
// 最小使用次数
long least = 0;
// 访问时间最早
long earliest = 0;
K toBeRemovedByCount = null;
K toBeRemovedByTime = null;
Iterator<Map.Entry<K, ValueEntry>> it = entries.iterator();
if (it.hasNext()) {
Map.Entry<K, ValueEntry> valueEntry = it.next();
least = valueEntry.getValue().count.get();
toBeRemovedByCount = valueEntry.getKey();
earliest = valueEntry.getValue().lastAccess.get();
toBeRemovedByTime = valueEntry.getKey();
}
while (it.hasNext()) {
Map.Entry<K, ValueEntry> valueEntry = it.next();
if (valueEntry.getValue().count.get() < least) {
least = valueEntry.getValue().count.get();
toBeRemovedByCount = valueEntry.getKey();
}
if (valueEntry.getValue().lastAccess.get() < earliest) {
earliest = valueEntry.getValue().count.get();
toBeRemovedByTime = valueEntry.getKey();
}
}
// System.out.println("remove:" + toBeRemoved);
// 如果最少使用次数大于MINI_ACCESS,那么移除访问时间最早的项(也就是最久没有被访问的项)
if (least > MINI_ACCESS) {
map.remove(toBeRemovedByTime);
} else {
map.remove(toBeRemovedByCount);
}
} public V get(K key) {
try {
readLock.lock();
V value = null;
ValueEntry valueEntry = map.get(key);
if (valueEntry != null) {
// 更新访问时间戳
valueEntry.updateLastAccess();
// 更新访问次数
valueEntry.count.incrementAndGet();
value = valueEntry.value;
}
return value;
} finally {
readLock.unlock();
}
} public void clear() {
try {
writeLock.lock();
map.clear();
} finally {
writeLock.unlock();
}
} public int size() {
try {
readLock.lock();
return map.size();
} finally {
readLock.unlock();
}
} public long getCount(K key) {
try {
readLock.lock();
ValueEntry valueEntry = map.get(key);
if (valueEntry != null) {
return valueEntry.count.get();
}
return 0;
} finally {
readLock.unlock();
}
} public Collection<Map.Entry<K, V>> getAll() {
try {
readLock.lock();
Set<K> keys = map.keySet();
Map<K, V> tmp = new HashMap<K, V>();
for (K key : keys) {
tmp.put(key, map.get(key).value);
}
return new ArrayList<Map.Entry<K, V>>(tmp.entrySet());
} finally {
readLock.unlock();
}
} class ValueEntry implements Serializable {
private V value; private AtomicLong count; private AtomicLong lastAccess; public ValueEntry(V value) {
this.value = value;
this.count = new AtomicLong(0);
lastAccess = new AtomicLong(System.nanoTime());
} public void updateLastAccess() {
this.lastAccess.set(System.nanoTime());
} }
}

参考:

简单LRU算法实现缓存-update2

LinkedHashMap 和 LRU算法实现的更多相关文章

  1. LinkedHashMap实现LRU算法

    LinkedHashMap特别有意思,它不仅仅是在HashMap上增加Entry的双向链接,它更能借助此特性实现保证Iterator迭代按照插入顺序(以insert模式创建LinkedHashMap) ...

  2. 用LinkedHashMap实现LRU算法

    (在学习操作系统时,要做一份有关LRU和clock算法的实验报告,很多同学都应该是通过数组去实现LRU,可能是对堆栈的使用和链表的使用不是很熟悉吧,在网上查资料时看到了LinkedHashMap,于是 ...

  3. 通过LinkedHashMap实现LRU算法

    一.基于LinkedHashMap源码分析 方法调用流程(这里只是以put方法位例) put() -> putVal() -> afterNodeInsertion() -> rem ...

  4. Java集合详解5:深入理解LinkedHashMap和LRU缓存

    今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 摘要: HashMap和双向链表合二为一即是LinkedHashMap.所谓Linke ...

  5. Guava---缓存之LRU算法

    随笔 - 169  文章 - 0  评论 - 292 GuavaCache学习笔记一:自定义LRU算法的缓存实现   前言 今天在看GuavaCache缓存相关的源码,这里想到先自己手动实现一个LRU ...

  6. 借助LinkedHashMap实现基于LRU算法缓存

    一.LRU算法介绍 LRU(Least Recently Used)最近最少使用算法,是用在操作系统中的页面置换算法,因为内存空间是有限的,不可能把所有东西都放进来,所以就必须要有所取舍,我们应该把什 ...

  7. 如何用LinkedHashMap实现LRU缓存算法

    阿里巴巴笔试考到了LRU,一激动忘了怎么回事了..准备不充分啊.. 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的.LRU这个算法就是把最近一次 ...

  8. JDK自带的LinkedHashMap来实现LRU算法

    1 代码如下 public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> { private final i ...

  9. 基于LinkedhashMap实现的LRU算法

    LRU全称是Least Recently Used,即最近最久未使用的意思.LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小.也就是说,当限定的空间已存 ...

随机推荐

  1. UITableview 多行删除

    //  RootViewController.m #import "RootViewController.h"#import "NextViewController.h& ...

  2. Operand forms

    Operand forms Computer Systems A Programmer's Perspective Second Edition

  3. P1351 联合权值

    #include <bits/stdc++.h> using namespace std; const int maxn = 200005; vector<int> son[m ...

  4. Apache Kafka源码分析 - autoLeaderRebalanceEnable

    在broker的配置中,auto.leader.rebalance.enable (false) 那么这个leader是如何进行rebalance的? 首先在controller启动的时候会打开一个s ...

  5. Machine Learning in Action -- 树回归

    前面介绍线性回归,但实际中,用线性回归去拟合整个数据集是不太现实的,现实中的数据往往不是全局线性的 当然前面也介绍了局部加权线性回归,这种方法有些局限 这里介绍另外一种思路,树回归 基本思路,用决策树 ...

  6. Python - 求斐波那契数列前N项之和

    n = int(input("Input N: ")) a = 0 b = 1 sum = 0 for i in range(n): sum += a a, b = b, a + ...

  7. 搭建C语言开发环境

    大学的时候有数据结构这门课,但...终究还是得学.电脑是win8的,根据网上的教程倒是能安装成功vc6.0并且能够打开新建工程,但是一编译运行就提示兼容性问题. 首先安装C语言编译器.下载MinGw ...

  8. spring容器IOC创建对象<三>

    问题?Spring的DI讲解.DI有几种注入方式.一.spring的DI:依赖注入给属性赋值DI定义:一个对象类的属性可以使用springDI(依赖注入)来进行赋值,但是并不是所有的类属性都适合spr ...

  9. http协议(转)

    主要还是为了存放状态码··· 剖析 HTTP 协议   目录 HTTP 概述 HTTP 消息结构 HTTP 请求 HTTP 响应 HTTP 状态码 参考 回到顶部 HTTP 概述 HTTP 是什么? ...

  10. Simplest way to serve static data from outside the application server in a Java web application

    tomcat service.xml <Context docBase="/path/to/images" path="/images" /> re ...