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

本文介绍了如何使用Java实现一个简单的基于LRU(Last Recent Used)原则的内存对象缓存。针对Android应用场景,如用户信息缓存,讨论了缓存设计的思路、源码、示例以及可能的改进方案,强调了效率和对象数量控制的重要性。
摘要由CSDN通过智能技术生成

常遇到需要将对象在内存中缓存的场景.比如下面场景:

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只适用于对象数较少时,比如要存一万个对象的话,清理缓存的工作应该放在线程中去做.

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

后期还需改进.



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值