36区技术博客

android 仿redis的简易设计及代码实现

一、前言

最近博主在做一款控制停车场摄像头和道闸的Android app,该app的主要职责是根据相机识别出来的车牌为参数,请求服务器后根据返回结果做相应的操作,比如是否开闸放行,或者缴费后出行等,另外还有播放语音和刷新屏幕等功能。

如果停车场的环境比较简单,那么只需要单相机就能满足需求,单相机的逻辑比较简单,流程上看不存在并发,所以行为是线性的。一个简单的流程如下:

但停车场的环境不可能是一成不变的,业主的需要也多种多样。尽管当前相机的识别率已经达到90%,但有些车流量大的地方,90%的的识别率仍然不能满足需求。另外,相机角度也会降低识别率。为了满足业主的需求,有些场合需要双摄像头。

双相机就意味着有多个出牌组合,这里将所有的出牌组合列出如下(A和B指代相机):

面对A和B同时出牌的情况,需要对结果进行过滤,而且A和B的出牌的间隔时间不会太一样,但一般在0-3秒之间。
这个时候想起了redis这个神器,但查了半天并没有人造出这个轮子,无奈只能参考一个网友的设计进行改良,自己造一个轮子,达到既能缓存又能定期自清理的目的。

二、redis设计要求

根据业务和技术需求,我们来总结一下简易redis的需求:

1、按照key-value的容器设计方法
2、快速查询和快速更新(删除),查询速度应该在O(n)以内,最好能够达到O(1)
3、有过期时间,在过期时间内能够获取到值,时间外则自行删除

碍于Android本身的计算能力比较弱(相比服务器级别的计算机而言),笔者不打算实现第三点要求,而且放到取值的时候再判断值是否有效。

另外还要考虑到Android上的内存比较小,不可能无限存储缓存值,需要自动清理一些长时间用不到的数据,但又不能启用一个线程进行扫描。基于这个考虑,笔者想到了Android的标准LRU组件。

结合上面的思路,我们很容易得到代码示例。

三、代码实现

public class MemCache<K, V> {

    private long defaultDuring = DateUtil.TIME_UNIT_SECOND * 120;
    private final LruCache<K, CacheItem<K, V>> lruCache;

    /**
     * construct.
     *
     * @param maxSize maxSize
     */
    public MemCache(int maxSize) {
        lruCache = new LruCache<>(maxSize);
    }

    /**
     * construct
     *
     * @param maxSize       maxSize
     * @param defaultDuring defaultDuring(milliseconds)
     */
    public MemCache(int maxSize, long defaultDuring) {
        this(maxSize);
        this.defaultDuring = defaultDuring;
    }

    /**
     * get value by key
     *
     * @param key key
     * @return value, return null when not found or value expired
     */
    public V get(@NonNull K key) {
        CacheItem<K, V> cacheItem = lruCache.get(key);

        if (cacheItem == null) {
            return null;
        }

        if (DateUtil.isCacheItemAlive(cacheItem)) {
            return cacheItem.getValue();
        } else {
            lruCache.remove(key);
            return null;
        }
    }

    /**
     * put a value by key
     *
     * @param key    key
     * @param value  value
     * @param during during(milliseconds)
     * @return previous value, return null if not found
     */
    public V put(@NonNull K key, @NonNull V value, long during) {
        if (during < 0) {
            throw new IllegalArgumentException("during should >= 0");
        }

        Date date = new Date();
        long time = date.getTime();
        CacheItem<K, V> cacheItem = new CacheItem<>(key, value, time, time, time + during);

        CacheItem<K, V> previous = lruCache.put(key, cacheItem);

        if (previous != null) {
            //这句话没看懂
            cacheItem.setCreateTime(previous.getCreateTime());
            if (DateUtil.isCacheItemAlive(previous)) {
                return previous.getValue();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * put a value by key(during = defaultDuring)
     *
     * @param key   key
     * @param value value
     * @return previous value, return null if not found
     */
    public V put(@NonNull K key, @NonNull V value) {
        return put(key, value, defaultDuring);
    }

    /**
     * remove value by key
     *
     * @param key key
     * @return removed value, return null if not found
     */
    public V remove(@NonNull K key) {
        CacheItem<K, V> remove = lruCache.remove(key);
        if (remove == null) {
            return null;
        }

        return remove.getValue();
    }
}

在代码中只需要像使用map一样使用MemCache即可,不过由于这里底层用到的是LRU组件,需要给它设置一个大小值,最后LRU会根据使用的频次来自动删除一些记录。

四、总结

Android防redis的设计其实用到了一个节点上带有信息的小技巧实现,本身并不复杂。

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »