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

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

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

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

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

    /**
     * 保存会过期key的过期时间
     */
    private HashMap 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 Comparatorsuper 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, Comparatorsuper 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 Comparatorsuper K> comparator() {
        return this.comparator;
    }

    @Override
    public SortedMap 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 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 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(Mapextends 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 keySet() {
        //先删除过期数据
        this.removeExpireData("keySet");
        try {
            readLock.lock();
            if (enableSort) {
                return this.treeMap.keySet();
            }
            return this.hashMap.keySet();
        } finally {
            readLock.unlock();
        }
    }

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

    @Override
    public Set> 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 keys = this.expireKeysMap.get(expireTime);
                if (keys != null) {
                    keys.remove(key);
                }
            }
            expireTime = System.currentTimeMillis() + ms;
            this.keyExpireMap.put(key, expireTime);
            List 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 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 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 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> 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 timeKeys = new ArrayList<>();
        List keys = new ArrayList<>();
        for (Entry> 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。

Original: https://www.cnblogs.com/hdwang/p/16414103.html
Author: 追极
Title: 写了一个简易的本地缓存fastmap,支持键过期和键排序等

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/587763/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 《我是面试官》设计模式-单例模式

    设计模式-单例模式 《巫师3》中,陪着主人公南征北战的坐骑,不管你何时何地召唤它,它永远只有一个名字——萝卜。 大家好,我是左耳朵梵高。文章首发于微信公众号「左耳朵梵高」,欢迎关注…

    数据结构和算法 2023年6月7日
    099
  • CF 793 div2

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    数据结构和算法 2023年6月12日
    069
  • Java中高亮字符串中指定的关键字

    本文主要提供一种在字符串中为给定关键字提供拼接高亮标签,该方法支持大小写忽略匹配一下高亮演示网址:www.huhailong.vip 最终效果 ; 实现代码 该方法主要用于在一串字…

    数据结构和算法 2023年6月8日
    0112
  • P5080 Tweetuzki 爱序列 题解

    题目大意 Tweetuzki 有一个长度为 (n) 的序列 (a_1, a_2, \cdots, a_n)。他希望找出一个最大的 (k),满足在原序列中存在一些数 (b_1, b_…

    数据结构和算法 2023年6月7日
    0102
  • AcWing 175. 电路维修(搜索)

    题目描述 题目链接 题目大意 找到一条从左上角到右下角的通路 旋转格子次数最少 解题思路 运用双端队列广搜 不旋转则权重为0,旋转则权重为1 从队头扩展出的边的权重为0时,插到队头…

    数据结构和算法 2023年6月16日
    087
  • 【重要】LeetCode 565. 数组嵌套

    题目链接 565. 数组嵌套 注意事项 题面形式类似于并查集,每个元素都是从i到nums[i]的有向边,相连的元素形成一条链。 从一个元素切入,然后一直走到对应链的末尾,统计这条链…

    数据结构和算法 2023年6月8日
    095
  • Visual Studio 2022 Community 不完全攻略

    在安装页面, 左下角可以修改安装位置 (下载缓存可以不保留, 但必须要和本体放在不同的文件夹中). 编写基本的 C++ 程序只需勾选 使用 C++ 的桌面开发 这个工作负荷. 语言…

    数据结构和算法 2023年6月12日
    0135
  • ABC 267 F Exactly K Steps(树的直径,LCA倍增)

    题目: ​ 给出一棵n个点的树,边权为1,进行2e5次询问,每次输出 任意一个离结点(u)距离为(k)的结点。 思路: ​ 对于树上问题,我们的武器不多,而且时间复杂度为O(log…

    数据结构和算法 2023年6月12日
    0101
  • P3384 【模板】轻重链剖分/树链剖分

    树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。 具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。 重链剖分能保证划分出的每条链上的…

    数据结构和算法 2023年6月8日
    0142
  • 使用缓存的原则

    原则:没事不要用缓存 引入缓存后的不良后果: 缓存和数据库双写不一致 缓存雪崩、缓存穿透、缓存击穿 缓存并发竞争 适用场景: 读密集型 存在热数据 对响应时间要求高 对一致性要求不…

    数据结构和算法 2023年6月7日
    0110
  • Web入门(2)——HTML基础

    HTML到底是什么? HTML元素详解 元素结构 元素属性(Attribute) 嵌套元素 空元素 HTLM文档详解 标记文本 标题(Heading) 段落(Paragraph) …

    数据结构和算法 2023年6月7日
    0131
  • 状态压缩DP

    状态压缩DP,是今天所要讲到的内容。 其实状态压缩这个概念我们并不陌生,我们之前在做八数码问题的时候就是把那张图给压缩成了一串数字来表示,这里其实也是利用到了状态压缩,让图的内容可…

    数据结构和算法 2023年6月7日
    085
  • 剑指 Offer 17. 打印从1到最大的n位数

    剑指 Offer 17. 打印从1到最大的n位数注意这里的n是表示位数,所以最大的数也就是枚举每一位都是9的情况。最后从1枚举到最后一位即可。 class Solution { p…

    数据结构和算法 2023年6月7日
    090
  • 单例模式

    使用最广同时也是面试问的最多的一个设计模式 代码: 还有一种方式:静态局部变量实现的懒汉式,且线程安全的模式。 某天面试被问到单例模式有什么缺点,没了解过,只是说优点还是蛮多的。 …

    数据结构和算法 2023年6月8日
    097
  • 逆元

    逆元定义:若(a*x=1(\mod b)) 且(a,b)互质,则称(x)为(a)的逆元,记作(a^{-1}) 逆元应用 求((t/a)\mod b) 时,转化为(t*a^{-1} …

    数据结构和算法 2023年6月7日
    0112
  • 深拷贝与浅拷贝

    1.浅拷贝 简单的赋值拷贝操作如果利用编译器提供的拷贝构造函数,会做浅拷贝操作浅拷贝带来的问题就是堆区的内存重复释放解决办法是深拷贝 // &#x6D45;&#x6…

    数据结构和算法 2023年6月7日
    088
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球