借用HashMap底层实现本地缓存

本文描述了一个项目中如何使用Java实现看板数据的缓存,考虑了多筛选条件的主子key缓存需求,最终采用了自定义的横向数组和纵向链表结构,结合ArrayList和HashMap特性,以及Redis的启发,确保高效且适应不同场景的数据存储和管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

  • 由于项目中有一个看板需求,有很多个图表、指标数据展示,所以考虑用缓存,考虑本项目是个单节点部署,所以优先考虑使用本地缓存。

考虑

  • 每个图表、指标都有多个筛选条件组合,所以考虑主子key缓存,首先考虑的是redis,其实不太适合,虽然有对应的数据结构(HASH),但是子key我需要是一个Object对象,而不是一个筛选条件的字符串拼接(而且redis对key的长度也是有要求的)。

实现逻辑

  • 借鉴arraylist的扩容机制;
  • 借鉴hashmap的存储机制,使用横向数组,纵向链表进行存储;
  • 使用读写分段锁减少锁的粒度
  • 原先hashmap 需要对key进行hash从而找到位置,但是在这里是直接让业务场景绑定指定的位置,比如标的的出险频率这个图表 直接指定使用index=1的位置,将不同的筛选条件都存在这个位置上。

代码实现

/**
 * 用作看板数据缓存,想采用数据加链表的结构进行存储
 *
 * @author xzy
 * @date 2023-12-29
 */
@Slf4j
public class Cache<K, V> implements Serializable {

    /**
     * 数组默认大小
     */
    private final static int DEFAULT_CAPACITY = 16;
    /**
     * 数组最大容量
     */
    private final static int MAX_CAPACITY = 128;

    /**
     * 缓存5分钟
     */
    private final static int DEFAULT_EXPIRATION = 1000 * 60 * 5;
    private static final long serialVersionUID = 7509771018891971331L;
    /**
     * 用来做数据缓存,横向数组、纵向列表
     */
    private volatile Node<K, V>[] table;

    /**
     * 当前元素个数,横纵向元素总数
     */
    @Getter
    private final AtomicInteger size = new AtomicInteger(0);

    /**
     * 数组的长度
     */
    @Getter
    private volatile int capacity;

    /**
     * 缓存过期时间
     */
    private final int expirationTime;

    /**
     * 全局读写锁
     */
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 每个index一个读写锁,就是对锁进行分段,使其粒度更小
     */
    private final ConcurrentHashMap<Integer, ReentrantReadWriteLock> tabReadWriteLockMap;

    public Cache() {
        this(DEFAULT_CAPACITY, DEFAULT_EXPIRATION);
    }

    public Cache(int capacity, int expirationTime) {
        if (capacity <= 0 || expirationTime <= 0) {
            throw new IllegalArgumentException("The capacity and expirationTime param is illegal.");
        }
        this.capacity = Math.min(capacity, MAX_CAPACITY);
        this.table = new Node[this.capacity];
        this.expirationTime = expirationTime;
        this.tabReadWriteLockMap = new ConcurrentHashMap<>(this.capacity);
        for (int i = 0; i < this.capacity; i++) {
            this.tabReadWriteLockMap.put(i, new ReentrantReadWriteLock());
        }
        // 启动一个线程去清理缓存
        new Thread(() -> {
            try {
                while (true) {
                    for (int i = 0; i < this.capacity; i++) {
                        getWriteLock(i).lock(); // 分段加锁
                        try {
                            Node<K, V> prev = null;
                            Node<K, V> current = table[i];
                            long now = System.currentTimeMillis(); // 缓存当前时间
                            while (current != null) {
                                if (current.getExpireTime() < now) {
                                    if (prev == null) {
                                        table[i] = current.next;
                                    } else {
                                        prev.next = current.next;
                                    }
                                    size.decrementAndGet();
                                } else {
                                    prev = current;
                                }
                                current = current.next;
                            }
                        } finally {
                            getWriteLock(i).unlock(); // 分段解锁
                        }
                    }
                    Thread.sleep(1000 * 60); // 休眠 1 分钟
                }
            } catch (InterruptedException e) {
                log.error("清理线程被中断,停止运行。");
            }
        }).start();
    }

    /**
     * 获取指定位置的写锁
     *
     * @param index 数组中的位置
     * @return
     */
    private ReentrantReadWriteLock.WriteLock getWriteLock(int index) {
        if (!tabReadWriteLockMap.containsKey(index)) {
            if (index > this.capacity - 1) {
                log.warn("索引越界了,需要扩容了,使用全局锁。");
                return readWriteLock.writeLock();
            }
        }
        return tabReadWriteLockMap.get(index).writeLock();
    }

    /**
     * 获取指定位置的读锁
     *
     * @param index 数组中的位置
     * @return
     */
    private ReentrantReadWriteLock.ReadLock getReadLock(int index) {
        if (!tabReadWriteLockMap.containsKey(index)) {
            if (index > this.capacity - 1) {
                log.warn("索引越界了,需要扩容了,使用全局锁。");
                return readWriteLock.readLock();
            }
        }
        return tabReadWriteLockMap.get(index).readLock();
    }

    /**
     * 添加元素
     *
     * @param k     key
     * @param v     value
     * @param index 位置,直接不用计算hash
     */
    public void put(K k, V v, int index) {
        ReentrantReadWriteLock.WriteLock writeLock = this.getWriteLock(index);
        writeLock.lock();
        try {
            Node<K, V> n = new Node<>(k, v, null, System.currentTimeMillis() + expirationTime);
            if (index >= this.capacity) {
                // 需要扩容
                grow();
            }

            Node<K, V> c = table[index];
            if (c == null) {
                table[index] = n;
            } else {
                // 获取最后一个元素
                Node<K, V> l = c.findLastNode();
                l.setNext(n);
            }
            size.incrementAndGet();
        } finally {
            writeLock.unlock();
        }

    }

    /**
     * 获取元素
     *
     * @param k     key
     * @param index 位置
     * @return
     */
    public V get(K k, int index) {
        ReentrantReadWriteLock.ReadLock readLock = this.getReadLock(index);
        readLock.lock();
        try {
            if (index >= this.capacity) {
                return null;
            }
            Node<K, V> current = table[index];
            while (current != null) {
                if (current.getKey().equals(k)) {
                    if (current.getExpireTime() < System.currentTimeMillis()) {
                        return null;
                    }
                    return current.getValue();
                }
                current = current.getNext();
            }
            return null;
        } finally {
            readLock.unlock();
        }
    }


    /**
     * 扩容,此时就使用全局写锁
     */
    public void grow() {
        readWriteLock.writeLock().lock();
        try {
            if (this.capacity == MAX_CAPACITY) {
                throw new BaseException("The capacity max length 128");
            }
            int oldCapacity = this.capacity;
            this.capacity = Math.min(this.capacity + (this.capacity >> 1), MAX_CAPACITY);
            Node<K, V>[] newTab = new Node[this.capacity];
            System.arraycopy(table, 0, newTab, 0, table.length);
            table = newTab;
            for (int i = oldCapacity; i < this.capacity; i++) {
                this.tabReadWriteLockMap.put(oldCapacity, new ReentrantReadWriteLock());
            }
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }


    @Getter
    @Setter
    private static class Node<K, V> {

        K key;

        V value;

        Node<K, V> next;

        long expireTime;

        public Node(K key, V value, Node<K, V> next, long expireTime) {
            this.key = key;
            this.value = value;
            this.next = next;
            this.expireTime = expireTime;
        }

        private synchronized Node<K, V> findLastNode() {
            Node<K, V> l = this;
            while (l.getNext() != null) {
                l = l.getNext();
            }
            return l;
        }

    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值