1. 程式人生 > >java實現帶過期時間的快取

java實現帶過期時間的快取

直接上程式碼
package com.dyh.utils;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.PriorityQueue;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

public class LocalCache {
    private static ScheduledExecutorService swapExpiredPool
            = new ScheduledThreadPoolExecutor(10);

    private ReentrantLock lock = new ReentrantLock();

    private ConcurrentHashMap<String, Node> cache = new ConcurrentHashMap<>(1024);
    /**
     * 讓過期時間最小的資料排在佇列前,在清除過期資料時
     * ,只需檢視快取最近的過期資料,而不用掃描全部快取
     *
     * @see Node#compareTo(Node)
     * @see SwapExpiredNodeWork#run()
     */
    private PriorityQueue<Node> expireQueue = new PriorityQueue<>(1024);

    public LocalCache() {

        //使用預設的執行緒池,每5秒清除一次過期資料
        //執行緒池和呼叫頻率 最好是交給呼叫者去設定。
        swapExpiredPool.scheduleWithFixedDelay(
                new SwapExpiredNodeWork(), 5, 5, TimeUnit.SECONDS);
    }

    public Object set(String key, Object value, long ttl) {

        Assert.isTrue(StringUtils.hasLength(key), "key can't be empty");
        Assert.isTrue(ttl > 0, "ttl must greater than 0");

        long expireTime = System.currentTimeMillis() + ttl;
        Node newNode = new Node(key, value, expireTime);
        lock.lock();
        try {
            Node old = cache.put(key, newNode);
            expireQueue.add(newNode);
            //如果該key存在資料,還要從過期時間佇列刪除
            if (old != null) {
                expireQueue.remove(old);
                return old.value;
            }
            return null;
        } finally {
            lock.unlock();
        }

    }

    /**
     * 拿到的資料可能是已經過期的資料,可以再次判斷一下
     * if(n.expireTime<System.currentTimeMillis()){
     * return null;
     * }
     * 也可以直接返回整個節點Node ,交給呼叫者去取捨
     * <p>
     * <p>
     * 無法判斷不存在該key,還是該key存的是一個null值,如果需要區分這兩種情況
     * 可以定義一個全域性標識,標識key不存在
     * public static final NOT_EXIST = new Object();
     * 返回值時
     * return n==null?NOT_EXIST:n.value;
     */
    public Object get(String key) {
        Node n = cache.get(key);
        return n == null ? null : n.value;
    }

    /**
     * 刪出KEY,並返回該key對應的資料
     */
    public Object remove(String key) {
        lock.lock();
        try {
            Node n = cache.remove(key);
            if (n == null) {
                return null;
            } else {
                expireQueue.remove(n);
                return n.value;
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 刪除已經過期的資料
     */
    private class SwapExpiredNodeWork implements Runnable {

        @Override
        public void run() {

            long now = System.currentTimeMillis();
            while (true) {
                lock.lock();
                try {
                    Node node = expireQueue.peek();
                    //沒有資料了,或者資料都是沒有過期的了
                    if (node == null || node.expireTime > now) {
                        return;
                    }
                    cache.remove(node.key);
                    expireQueue.poll();

                } finally {
                    lock.unlock();
                }
            }
        }
    }


    private static class Node implements Comparable<Node> {
        private String key;
        private Object value;
        private long expireTime;

        public Node(String key, Object value, long expireTime) {
            this.value = value;
            this.key = key;
            this.expireTime = expireTime;
        }


        /**
         * @see SwapExpiredNodeWork
         */
        @Override
        public int compareTo(Node o) {
            long r = this.expireTime - o.expireTime;
            if (r > 0) {
                return 1;
            }
            if (r < 0) {
                return -1;
            }
            return 0;
        }

    }
}

有啥不足或者錯誤的地方歡迎留言哈。

另外,在PriorityQueue的時候,發現它的預設初始話容量是11,不知道有啥深意,知道的老哥指導下。。