一、前言
最近博主在做一款控制停车场摄像头和道闸的Android app,该app的主要职责是根据相机识别出来的车牌为参数,请求服务器后根据返回结果做相应的操作,比如是否开闸放行,或者缴费后出行等,另外还有播放语音和刷新屏幕等功能。
如果停车场的环境比较简单,那么只需要单相机就能满足需求,单相机的逻辑比较简单,流程上看不存在并发,所以行为是线性的。一个简单的流程如下:
但停车场的环境不可能是一成不变的,业主的需要也多种多样。尽管当前相机的识别率已经达到90%,但有些车流量大的地方,90%的的识别率仍然不能满足需求。另外,相机角度也会降低识别率。为了满足业主的需求,有些场合需要双摄像头。
双相机就意味着有多个出牌组合,这里将所有的出牌组合列出如下(A和B指代相机):
- 1、A和B同时出牌,出的牌可能一样,可能不一样。
- 2、A出牌,B没有出牌,反之亦然。
- 3、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的设计其实用到了一个节点上带有信息的小技巧实现,本身并不复杂。
评论已关闭