写了一个简易的本地缓存fastmap,支持键过期和键排序等

背景

  一般我们可以用HashMap做本地缓存,但是HashMap功能比较弱,不支持Key过期,不支持数据范围查找等。故在此实现了一个简易的本地缓存,取名叫fastmap。

 

项目地址

github: https://github.com/hdwang123/fastmap

gitee: https://gitee.com/hdwang123/fastmap 

 

功能

1.支持数据过期

2.支持等值查找

3.支持范围查找

4.支持key排序

 

实现思路

1.等值查找采用HashMap
2.范围查找采用TreeMap
3.数据过期实现:调用相关查询方法时清理过期Key 定时(每秒)清理一遍过期Key
4.使用两个ReentrantReadWriteLock的读写锁实现线程安全,一个用于数据的CRUD,一个用于过期key的维护

 

核心代码

package com.hdwang.fastmap;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author wanghuidong
 * 时间: 2022/6/26 10:10
 */
public class FastMap<K, V> implements IFastMap<K, V> {

    /**
     * 主要运用于等值查找
     */
    private HashMap<K, V> hashMap = new HashMap<>();

    /**
     * 主要运用于范围查找
     */
    private TreeMap<K, V> treeMap = new TreeMap<>();

    /**
     * 按照时间顺序保存了会过期key集合,为了实现快速删除,结构:时间戳->key列表
     */
    private TreeMap<Long, List<K>> expireKeysMap = new TreeMap<>();

    /**
     * 保存会过期key的过期时间
     */
    private HashMap<K, Long> keyExpireMap = new HashMap<>();

    /**
     * 是否启用排序(默认不启用)
     */
    boolean enableSort = false;

    /**
     * 是否启用数据过期功能(默认启用)
     */
    boolean enableExpire = true;

    /**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     *
     * @serial
     */
    private final Comparator<? super K> comparator;

    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //数据写锁
    private final Lock writeLock = readWriteLock.writeLock();

    //数据读锁
    private final Lock readLock = readWriteLock.readLock();

    private ReentrantReadWriteLock expireKeysReadWriteLock = new ReentrantReadWriteLock();

    //过期key写锁
    private final Lock expireKeysWriteLock = expireKeysReadWriteLock.writeLock();

    //过期key读锁
    private final Lock expireKeysReadLock = expireKeysReadWriteLock.readLock();

    private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);

    private static int serialNumber() {
        return nextSerialNumber.getAndIncrement();
    }

    /**
     * 默认构造器,不启用排序
     */
    public FastMap() {
        this.comparator = null;
        this.enableSort = false;
        this.enableExpire = true;
        this.init();
    }

    /**
     * 构造器,enableExpire配置是否启用过期
     *
     * @param enableExpire 是否启用过期
     */
    public FastMap(boolean enableExpire) {
        this.comparator = null;
        this.enableSort = false;
        this.enableExpire = enableExpire;
        this.init();
    }

    /**
     * 构造器,enableExpire配置是否启用过期,enableSort配置是否启用排序
     *
     * @param enableExpire 是否启用过期
     * @param enableSort   是否启用排序
     */
    public FastMap(boolean enableExpire, boolean enableSort) {
        this.comparator = null;
        this.enableExpire = enableExpire;
        this.enableSort = enableSort;
        this.init();
    }

    /**
     * 构造器,启用排序,排序器由自己传入
     *
     * @param comparator 排序器
     */
    public FastMap(boolean enableExpire, Comparator<? super K> comparator) {
        this.enableExpire = enableExpire;
        this.comparator = comparator;
        this.enableSort = true;
        this.init();
    }

    /**
     * 初始化
     */
    public void init() {
        if (this.enableExpire) {
            //启用定时器,定时删除过期key,1秒后启动,定时1秒
            Timer timer = new Timer("expireTask-" + serialNumber(), true);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    removeExpireData("timer");
                }
            }, 1000, 1000);
        }
    }


    @Override
    public Comparator<? super K> comparator() {
        return this.comparator;
    }

    @Override
    public SortedMap<K, V> subMap(K fromKey, K toKey) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("subMap");
        try {
            readLock.lock();
            return this.treeMap.subMap(fromKey, toKey);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public SortedMap<K, V> headMap(K toKey) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("headMap");
        try {
            readLock.lock();
            return this.treeMap.headMap(toKey);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public SortedMap<K, V> tailMap(K fromKey) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("tailMap");
        try {
            readLock.lock();
            return this.treeMap.tailMap(fromKey);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public K firstKey() {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("firstKey");
        try {
            readLock.lock();
            return this.treeMap.firstKey();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public K lastKey() {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("lastKey");
        try {
            readLock.lock();
            return this.treeMap.lastKey();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public int size() {
        //先删除过期数据
        this.removeExpireData("size");
        try {
            readLock.lock();
            return this.hashMap.size();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public boolean isEmpty() {
        //先删除过期数据
        this.removeExpireData("isEmpty");
        try {
            readLock.lock();
            return this.hashMap.isEmpty();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public boolean containsKey(Object key) {
        //先删除过期数据
        this.removeExpireData("containsKey");
        try {
            readLock.lock();
            return this.hashMap.containsKey(key);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public boolean containsValue(Object value) {
        //先删除过期数据
        this.removeExpireData("containsValue");
        try {
            readLock.lock();
            return this.hashMap.containsValue(value);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public V get(Object key) {
        //先删除过期数据
        this.removeExpireData("get");
        try {
            readLock.lock();
            return this.hashMap.get(key);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public V put(K key, V value) {
        try {
            writeLock.lock();
            V val = this.hashMap.put(key, value);
            if (enableSort) {
                val = this.treeMap.put(key, value);
            }
            return val;
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public V remove(Object key) {
        try {
            writeLock.lock();
            V val = this.hashMap.remove(key);
            if (enableSort) {
                val = this.treeMap.remove(key);
            }
            return val;
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        try {
            writeLock.lock();
            this.hashMap.putAll(m);
            if (enableSort) {
                this.treeMap.putAll(m);
            }
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public void clear() {
        try {
            writeLock.lock();
            this.hashMap.clear();
            if (enableSort) {
                this.treeMap.clear();
            }
        } finally {
            writeLock.unlock();
        }
    }

    @Override
    public Set<K> keySet() {
        //先删除过期数据
        this.removeExpireData("keySet");
        try {
            readLock.lock();
            if (enableSort) {
                return this.treeMap.keySet();
            }
            return this.hashMap.keySet();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public Collection<V> values() {
        //先删除过期数据
        this.removeExpireData("values");
        try {
            readLock.lock();
            if (enableSort) {
                return this.treeMap.values();
            }
            return this.hashMap.values();
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        //先删除过期数据
        this.removeExpireData("entrySet");
        try {
            readLock.lock();
            if (enableSort) {
                return this.treeMap.entrySet();
            }
            return this.hashMap.entrySet();
        } finally {
            readLock.unlock();
        }
    }


    @Override
    public Long expire(K key, Long ms) {
        if (!enableExpire) {
            throw new RuntimeException("未启用过期功能");
        }
        try {
            expireKeysWriteLock.lock();

            //判断是否已经设置过过期时间
            Long expireTime = this.keyExpireMap.get(key);
            if (expireTime != null) {
                //清除之前设置的过期时间
                this.keyExpireMap.remove(key);
                List<K> keys = this.expireKeysMap.get(expireTime);
                if (keys != null) {
                    keys.remove(key);
                }
            }
            expireTime = System.currentTimeMillis() + ms;
            this.keyExpireMap.put(key, expireTime);
            List<K> keys = this.expireKeysMap.get(expireTime);
            if (keys == null) {
                keys = new ArrayList<>();
                keys.add(key);
                this.expireKeysMap.put(expireTime, keys);
            } else {
                keys.add(key);
            }
            return expireTime;
        } finally {
            expireKeysWriteLock.unlock();
        }
    }

    @Override
    public Long ttl(K key) {
        if (!enableExpire) {
            throw new RuntimeException("未启用过期功能");
        }
        V val = this.get(key);
        if (val == null) {
            //数据不存在,存活时间返回null
            return null;
        }
        try {
            expireKeysReadLock.lock();
            Long expireTime = this.keyExpireMap.get(key);
            return expireTime - System.currentTimeMillis();
        } finally {
            expireKeysReadLock.unlock();
        }
    }

    @Override
    public SortedMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("subMap");
        try {
            readLock.lock();
            return this.treeMap.subMap(fromKey, fromInclusive, toKey, toInclusive);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public SortedMap<K, V> headMap(K toKey, boolean inclusive) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("headMap");
        try {
            readLock.lock();
            return this.treeMap.headMap(toKey, inclusive);
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public SortedMap<K, V> tailMap(K fromKey, boolean inclusive) {
        if (!enableSort) {
            throw new RuntimeException("未启用排序");
        }
        //先删除过期数据
        this.removeExpireData("tailMap");
        try {
            readLock.lock();
            return this.treeMap.tailMap(fromKey, inclusive);
        } finally {
            readLock.unlock();
        }
    }

    /**
     * 删除过期的数据
     */
    private void removeExpireData(String flag) {
        if (!enableExpire) {
            return;
        }
        //查找过期key
        Long curTimestamp = System.currentTimeMillis();
        SortedMap<Long, List<K>> expiredKeysMap;
        try {
            expireKeysReadLock.lock();
            //过期时间在【从前至此刻】区间内的都为过期的key
            expiredKeysMap = this.expireKeysMap.headMap(curTimestamp, true);
//            System.out.println(String.format("thread:%s caller:%s removeExpireData【curTime=%s,expiredKeysMap=%s】", Thread.currentThread().getName(), flag, curTimestamp, expiredKeysMap));
        } finally {
            expireKeysReadLock.unlock();
        }

        //删除过期数据
        List<Long> timeKeys = new ArrayList<>();
        List<K> keys = new ArrayList<>();
        for (Entry<Long, List<K>> entry : expiredKeysMap.entrySet()) {
            timeKeys.add(entry.getKey());
            for (K key : entry.getValue()) {
                this.remove(key);

                keys.add(key);
            }
        }

        //清理过期key
        try {
            expireKeysWriteLock.lock();
            //删除过期key集合
            for (Long key : timeKeys) {
                this.expireKeysMap.remove(key);
            }

            for (K key : keys) {
                //删除过期key的过期时间戳
                this.keyExpireMap.remove(key);
            }
        } finally {
            expireKeysWriteLock.unlock();
        }
    }
}

 

注意:最新完整代码以github上为准,修复了相关bug。

 

posted @ 2022-06-26 18:51  追极  阅读(106)  评论(0编辑  收藏  举报