[原创] java简单实现redis定时容器

实现背景:
由于我之前使用python写了一个比价系统的爬虫,然后没想到还真的有人叫我提供接口,所以,我打算找时间写一下java版的提供接口给调用,但是我又怕大家很不礼貌的索取,所以我需要做一个ip时间限制,首先想到redis实现,redis用途很广泛,其中有一个API我很喜欢的就是给key设置有效时间。如果使用redis提供的 Java API 很简单。但是依赖环境也需要追加。但是我不想依赖太多,我只是想简单用一下。所以我就不想依赖redis了。最后使用Java简单实现一下。

思考:

  • 需要哪些方法?

    CRUD 肯定需要的,观察redis的数据结构和Map有点类似,所以我这里参考了Map接口的 API 做实现,主要提供以下方法:

    • public void put(String key, Object value)
    • public Object remove(String key)
    • public Object get(String key)
    • public boolean containsKey(String key)
    • public void clear()

    额外还提供以下方法进行设置

    • public void setThresholdSize(int thresholdSize) // 设置阈值
    • public void setOverTime(int overTime) // 设置超时毫秒值
    • public void setSync(boolean sync) // 设置是否超时
  • 需要注意哪些?

    由于redis设置超时key的话会定时删除数据,当然我没有去看源码,貌似redis也不是java写的,是ANSI C的吧,没记错的话。哈哈哈~~ 然后设计的时候我们也需要考虑删除数据,那么如何实现超时数据进行删除呢?想到了开一个线程轮巡数据,超时就删除数据的方式,但是我觉得这样子的话也比较耗资源,不值得,如果真正想好性能优化一点的话就用redis了,所以我这里不采用线程轮序的方式,而是采用一个阈值轮巡检查数据超时情况进行移除(有可能造成一部分僵尸数据的存在,但是不会有很大影响)。

    除了上面的清除数据,还需要考虑的就是数据同步问题,所以这里加上关键字volatile,还需要考虑线程安全问题,使用了ConcurrentHashMap 和 自己定制一个线程安全的LinkedHashMap

  • 代码如下:
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

/**
 * @description: 简单定时缓存
 * @author: houyu
 * @create: 2018-12-05 00:02
 *
 * 使用:
 *         SimpleTimeCache<String, Object> build = SimpleTimeCache.<String, Object>builder().setOverTime(2000).build();
 *         build.put("A1", "A1");
 *         build.put("A2", "A2");
 *         Thread.sleep(3000);
 *
 *         System.out.println("build.get(\"A1\") = " + build.get("A1"));
 *         System.out.println("build.keySet() = " + build.keySet());
 *         System.out.println("build.entrySet() = " + build.entrySet());
 *         build.forEach((k, v) -> System.out.println(k + "=>" + v));
 *         System.out.println("build.containsKey(\"A1\") = " + build.containsKey("A1"));
 *         build.put("A3", "A3");
 *
 *         System.out.println("build.containsKey(\"A3\") = " + build.containsKey("A3"));
 *         build.remove("A3");
 *         System.out.println("build.containsKey(\"A3\") = " + build.containsKey("A3"));
 */
public class SimpleTimeCache<K, V> {

    private Builder builder;

    public static class Builder<K, V> {
        /** 阈值 */
        private int thresholdSize = 500;
        /** 过时 (10秒) */
        private int overTime = 10 * 1000;

        public Builder<K, V> setThresholdSize(int thresholdSize) {
            this.thresholdSize = thresholdSize;
            return this;
        }
        public Builder<K, V> setOverTime(int millis) {
            this.overTime = millis;
            return this;
        }

        public SimpleTimeCache<K, V> build() {
            return new SimpleTimeCache<K, V>(this);
        }
        // public <K, V> SimpleTimeCache<K, V> build(Class<K> keyClass, Class<V> valueClass) {
        //     return new SimpleTimeCache<K, V>(this);
        // }
    }

    public static <K, V> Builder<K, V> builder() {
        return new Builder<K, V>();
    }

    /** 最后一个添加的时间 */
    private volatile long lastTime;
    /** 时间Map */
    private Map<K, Long> timeMap;
    /** 存储值得Map */
    private Map<K, V> valueMap;

    protected SimpleTimeCache(Builder builder) {
        this.builder = builder;
        valueMap = new ConcurrentHashMap<>(this.builder.thresholdSize);
        timeMap = new ConcurrentHashMap<>(this.builder.thresholdSize);
    }

    /** 检查并清除过时数据 */
    private void checkAndCleanStaleData() {
        if(System.currentTimeMillis() - lastTime > builder.overTime) {
            // 最后一次添加的数据已经超时
            this.clear();
        } else if(timeMap.size() > builder.thresholdSize) {
            // 容量达到阈值时,需要检查
            valueMap.keySet().forEach(v -> {
                if(System.currentTimeMillis() - timeMap.get(v) > builder.overTime) {
                    // 超时数据为僵尸数据,满足删除的条件
                    this.remove(v);
                }
            });
        }
    }

    /** 新增 */
    public void put(K key, V value) {
        if(key == null || value == null) {
            // ConcurrentHashMap 不可以存储 null 值
            return;
        }
        lastTime = System.currentTimeMillis();
        // 检查数据是否过期
        this.checkAndCleanStaleData();
        timeMap.put(key, System.currentTimeMillis());
        valueMap.put(key, value);
    }

    /** 删除 */
    public V remove(K key) {
        timeMap.remove(key);
        return valueMap.remove(key);
    }

    /** 获取 */
    public V get(K key) {
        this.checkAndCleanStaleData();
        return this.getOrDefault(key, null);
    }

    /** 获取 */
    public V getOrDefault(K key, V defaultValue) {
        return (timeMap.get(key) == null || System.currentTimeMillis() - timeMap.get(key) > builder.overTime) ? null : valueMap.getOrDefault(key, defaultValue);
    }

    /** 判断是否存在key */
    public boolean containsKey(K key) {
        return this.get(key) != null;
    }

    public void clear() {
        timeMap.clear();
        valueMap.clear();
    }

    public Set<K> keySet() {
        this.checkAndCleanStaleData();
        return valueMap.keySet();
    }

    public Set<Map.Entry<K, V>> entrySet() {
        this.checkAndCleanStaleData();
        return valueMap.entrySet();
    }

    public void forEach(BiConsumer<? super K, ? super V> action) {
        this.checkAndCleanStaleData();
        valueMap.forEach(action);
    }

}

使用方法在类描述中…

  • 讨论

    博客同步到 SHY BLOG
    如果你有更好的实现,联系我分享~~

    邮箱:272694308@qq.com

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值