首页 > 代码库 > 简单即用的临时Map容器(参考TimeCacheMap和RotatingMap)

简单即用的临时Map容器(参考TimeCacheMap和RotatingMap)

  因为业务需要,经常会缓存一些临时数据.比如:手机号发送验证码, 60s内同一个手机号不能重复发送验证码.查询航班信息,缓存1分钟热门查询数据....

  之前一直使用redis作为数据缓存,简单方便..但是如果是个小App,数据没有那么大,可能需要缓存的数据只有不到100KB,使用redis就大材小用

  最近一个项目上线的时候,老大跟我说:真的有必要用redis么..不行就先删掉吧..自己想了下,因为App入口有两个ip,不同机器,虽然业务量不大,为了session共享,还是上了

  如果只有一个入口(不存在数据共享问题),业务量还不大,还是用临时map好一些..

  参考apache的Storme项目TimeCacheMap和RotatingMap,抽取出自己方便可用的临时map容器,名字就叫RotatingCacheMap了..

  基于jdk1.8,老项目估计也用不到...传送一个TimeCacheMap和RotatingMap源码分析http://www.cnblogs.com/yanghuahui/p/3677117.html

package com.tianjixie.timcross.utils;

import java.time.Instant;
import java.util.HashMap;

/**
 * 临时数据容器
 * 数据存储 采用 HashMap数组(回收桶)
 *
 * @author timcross
 */
public class RotatingCacheMap<K, V> {
    //默认工作Map(回收桶) 不能小于3 (为保证最小工作Map数 > 1)
    private static final int MIN_BUCKET_NUM = 3;
    //最大容量
    private static final int MAXIMUM_SIZE = 10000;
    //默认超时(回收)时间(秒)
    private static final int ROTATE_TIME = 60 * 60 * 10;
    //默认回收检查频率(毫秒)
    private static final int SLEEP_TIME = 60 * 1000;
    //桶Map初始化容量
    private final int _mapInitCapacity;
    //桶Map初始化扩容百分比
    private final float _mapLoadFactor;
    //超时时间(秒)
    private final long _rotateTime;
    //桶最大索引
    private final int _bucketsMaxIndex;
    //待删除 数据检查 方法调用(默认null,不使用数据检查)
    private final CheckRemoveData<K, V> _checkRemove;
    //所有 元素Key:元素添加时间
    private final HashMap<K, Object[]> _addTime = new HashMap<K, Object[]>();
    //回收桶集合
    private HashMap<K, V>[] _buckets;
    //最后一次(桶)回收 时间
    private long _lastRotate = getNowSecend();

    public RotatingCacheMap() {
        this(MIN_BUCKET_NUM, ROTATE_TIME, SLEEP_TIME, null, 100, 0.8f);
    }

    /**
     * @param bucketNum       同时工作的Map(数据|回收桶)数量 默认 3
     * @param rotateTime      超时时间(秒) 默认 60*60*10 (10小时)
     * @param sleepTime       回收线程扫描间隔(毫秒) 默认 60000 (1分钟)
     * @param checkRemoveDate 待删除数据检查 默认null(不使用数据检查)
     * @param mapInitCapacity 默认新增 数据Map 大小
     * @param mapLoadFactor   默认新增 数据Map 重新散列所需百分比
     */
    public RotatingCacheMap(int bucketNum, long rotateTime, long sleepTime, CheckRemoveData<K, V> checkRemoveDate,
                            int mapInitCapacity, float mapLoadFactor) {

        _mapLoadFactor = mapLoadFactor;
        _mapInitCapacity = mapInitCapacity;
        _checkRemove = checkRemoveDate;
        _rotateTime = rotateTime;
        _bucketsMaxIndex = bucketNum - 1;

        //计算 桶位置变换 时间,先进先出
        long expireTime = rotateTime / bucketNum + 1;
        if (bucketNum < MIN_BUCKET_NUM) bucketNum = MIN_BUCKET_NUM;
        _buckets = new HashMap[bucketNum];
        for (int i = 0; i < bucketNum; i++) {
            _buckets[i] = new HashMap<>(mapInitCapacity, mapLoadFactor);
        }
        //启动 数据超时 回收线程
        Thread _cleaner = new Thread(() -> {
            while (true) {
                try {
                    Thread.currentThread().sleep(sleepTime);
                } catch (InterruptedException ex) {
                }
                long now = getNowSecend();
                //检测 桶位置变换时间 和 存储最大容量
                if (now - _lastRotate > expireTime || size() > MAXIMUM_SIZE) {
                    //启动 数据清理
                    new Thread(() -> rotate()).start();
                    _lastRotate = now;
                }
            }
        });
        _cleaner.setDaemon(true);
        _cleaner.start();
    }

    public boolean containsKey(K key) {
        Object[] addTime = _addTime.get(key);
        if (addTime == null) return false;
        if ((Instant.now().getEpochSecond() - (long) addTime[0]) > _rotateTime) {
            //数据 已超时
            remove(key);
            return false;
        }
        return true;
    }

    public int size() {
        return _addTime.size();
    }

    public V get(K key) {
        Object[] addTime = _addTime.get(key);
        if (addTime == null) return null;
        if ((Instant.now().getEpochSecond() - (long) addTime[0]) > _rotateTime) {
            //数据 已超时
            remove(key);
            return null;
        }
        return ((HashMap<K, V>) addTime[1]).get(key);
    }

    public void put(K key, V value) {
        Object[] objects = new Object[2];
        objects[0] = getNowSecend();
        objects[1] = _buckets[0];
        ((HashMap) objects[1]).put(key, value);

        Object[] lastPut = _addTime.get(key);
        _addTime.put(key, objects);
        if (lastPut != null && lastPut[1] != objects[1]) ((HashMap) lastPut[1]).remove(key);
        //检查 总数据量 是否过大
        if (size() > MAXIMUM_SIZE) new Thread(() -> rotate()).start();
    }

    public void remove(K key) {
        Object[] remove = _addTime.remove(key);
        if (remove != null) ((HashMap) remove[1]).remove(key);
    }

    public void update(K key) {
        Object[] update = _addTime.get(key);
        if (update == null) return;

        update[0] = getNowSecend();
        HashMap<K, V> bucket = _buckets[0];
        if (update[1] == bucket) return;

        V remove = (V) ((HashMap) update[1]).get(key);
        bucket.put(key, remove);
        ((HashMap) update[1]).remove(key);
        update[1] = bucket;
    }

    /**
     * 回收 末端桶
     * 暂 禁止手动调用
     *
     * @return 删除的Map(回收桶)
     */
    private void rotate() {
        HashMap<K, V> deadMap;
        deadMap = _buckets[_bucketsMaxIndex];
        HashMap[] hashMaps = new HashMap[_buckets.length];
        hashMaps[0] = new HashMap((_mapInitCapacity + _buckets[0].size()) / 2, _mapLoadFactor);
        for (int i = 0; i < _bucketsMaxIndex; i++) {
            hashMaps[i + 1] = _buckets[i];
        }

        deadMap.keySet().stream().forEach(key -> {
            Object[] remove = _addTime.remove(key);
            if (remove[1] != deadMap) _addTime.put(key, remove);
            if (_checkRemove != null) _checkRemove.check(key, deadMap.get(key));
        });
        //return deadMap;
    }

    /**
     * 获取当前时间
     *
     * @return*/
    private long getNowSecend() {
        return Instant.now().getEpochSecond();
    }

    /**
     * 数据检查接口
     *
     * @param <K> 待删除key
     * @param <V> 待删除value
     */
    public interface CheckRemoveData<K, V> {
        void check(K key, V val);
    }
}

 

简单即用的临时Map容器(参考TimeCacheMap和RotatingMap)