您的位置:首页 > 编程语言 > Java开发

Java 实现简单的内存对象LRU缓存

2016-12-24 23:13 656 查看
常遇到需要将对象在内存中缓存的场景.比如下面场景:

Android 即使通讯应用中,用户列表,对话页面,群聊页面等,都会有大量的用户信息展示需求,至少需要 名字,头像 等信息,需要从服务器获取.

而上述页面有高概率会再次访问,浏览的用户信息也有高概率再次曝光.

每次曝光都请求一次服务器显然太浪费,但将全部用户信息都缓存下来肯定也不合适,因此需要缓存用户信息.

于是自己做了一个简单的基于 LRU 规则的内存缓存容器.

LRU 即 Last Recent Used, 大意是最近被使用的对象最后被丢弃.

先上源码再扯其他:

源码:

package lx.af.utils.cache;

import android.support.annotation.NonNull;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;

/**
* author: lx
* date: 16-6-1
*
* simple memory object LRU cache. based on HashMap.
* usage of the cache is simple:
* specify a max object count and a purge threshold, then the cache is ready to go.
* or you can specify the max count, and the purge threshold will be set to max * 0.75.
*
* when to purge the cache:
* when add objects to the cache, total object count will be checked against max count.
* if exceeded, objects will be deleted due to access time order until count reaches threshold.
* access time of objects will be recorded on put() and get(), and items with shorter
* access time will get purged first.
* the purge operation will be done automatically.
*
* this class is threadsafe.
*
* @param <K> the type of keys maintained by cache. rule is the same as of HashMap.
* @param <V> the type of mapped values. rule is the same as of HashMap.
*/
public class SimpleMemLruCache<K, V> {

private HashMap<K, Wrapper<K, V>> mMap;
private int mMax;
private int mThreshold;

private final Object mLock = new Object();

/**
* create cache with max object count.
* the threshold will be set to max * 0.75.
* @param max max object count of the cache, exceed which will trigger object purge.
*/
public SimpleMemLruCache(int max) {
this(max, (int) (max * 0.75f));
}

/**
* create cache with both max object count and object purge threshold.
* threshold should not be greater then max, or else exception will be thrown.
* @param max max object count of the cache, exceed which will trigger object purge.
* @param threshold purge threshold: when object exceeds max count, object deletion
*                  will be triggered. oldest object will get deleted first,
*                  until object count reaches threshold.
*/
public SimpleMemLruCache(int max, int threshold) {
if (threshold > max) {
throw new IllegalArgumentException("threshold should not be greater than max");
}
mMax = max;
mThreshold = threshold;
mMap = new HashMap<>(max + 3);
}

/**
* put object to cache.
* when called, the object's last access time will be set to current time.
* @param key key, as in {@link HashMap#put(Object, Object)}
* @param value value, as in {@link HashMap#put(Object, Object)}
*/
public void put(K key, V value) {
synchronized (mLock) {
mMap.put(key, new Wrapper<>(key, value));
}
checkPrune();
}

/**
* get object from cache.
* when called, the object's last access time will be updated to current time.
* @param key key, as in {@link HashMap#get(Object)}
* @return value, as in {@link HashMap#get(Object)}
*/
public V get(K key) {
synchronized (mLock) {
Wrapper<K, V> wrapper = mMap.get(key);
if (wrapper != null) {
wrapper.update();
return wrapper.obj;
}
}
return null;
}

/**
* clear all cached objects.
*/
public void clear() {
synchronized (mLock) {
mMap.clear();
}
}

// check and purge objects
private void checkPrune() {
if (mMap.size() < mMax) {
return;
}
synchronized (mLock) {
// use list to sort the map first
LinkedList<Wrapper<K, V>> list = new LinkedList<>();
list.addAll(mMap.values());
Collections.sort(list);
// delete oldest objects
for (int i = list.size() - 1; i >= mThreshold; i --) {
Wrapper<K, V> wrapper = list.get(i);
mMap.remove(wrapper.key);
}
list.clear();
}
}

// wrapper class to record object's last access time.
private static class Wrapper<K, V> implements Comparable<Wrapper> {

K key;
V obj;
long updateTime;

Wrapper(K key, V obj) {
this.key = key;
this.obj = obj;
this.updateTime = System.currentTimeMillis();
}

void update() {
updateTime = System.currentTimeMillis();
}

@Override
public int compareTo(@NonNull Wrapper another) {
return (int) (updateTime - another.updateTime);
}
}

}


思路:

主要思路是,将 HashMap 作为内存容器, object 以键值对的形式存入这个 HashMap. 

当然存入 HashMap 时, value 被套了一层,以便记录最后访问时间.

缓存的构造接受两个参数: 最大对象数, 和清理对象时的对象数的阈值.

然后对象就可以不断存入. 每次有存入对象操作时,就会检查一次 HashMap 中的对象数是否超过了最大值.

如果超过最大值,则开始清理对象,直到对象数达到阈值.

这里用了两个参数来限定对象数目,是基于效率考虑:

如果只有一个对象最大数限制,那么在存入对象到达该最大数后,几乎每次存入都需要到缓存里找到那个最老的对象并删除之.

所以这里设置了一个阈值,比如最大对象数为100,阈值为75.那么在每次对象超过100时,就启动删除流程,删除到对象数为75.这样在接下来的25个对象存入动作中,都不会再触发删除操作,效率明显提高.

示例:

基于开篇提到的使用场景,大体使用方法如下:

// 创建缓存,最大对象数为100,阈值为75
SimpleMemLruCache<String, UserInfo> mUserCache = new SimpleMemLruCache<>(100, 75);

// 获取用户信息时,首先查看缓存内是否存在,不存在则请求服务器
String userId = "some_user_id";
UserInfo user = mUserCache.get(userId);
if (user == null) {
user = UserInfoRequest.get();
// 将请求到的用户信息放入缓存
mUserCache.put(userId, user);
}
displayUserInfo(user);


改进:

这个缓存实现是个超级简单的缓存实现,适用场景有限,有很多待改进的地方.

比如 LRU 算法一般不止会考虑更新时间那么简单,还会考虑被使用次数等.

比如这个cache只适用于对象数较少时,比如要存一万个对象的话,清理缓存的工作应该放在线程中去做.

清理操作放在线程中的话,同步也是问题.这样就要实现分步清理.

后期还需改进.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息