简单内存缓存工具类HashMap和Guava Cache

我们一般使用缓存都是利用第三方,例如redis ,MemCache 等,这些都是要在项目中引入其它的东西框架的,有时候其实我们可以使用一个本地缓存来解决我们的需求,下面就是我借鉴高人实现的方案。

HashMap的实现

工具类主要采用 HashMap+定时器线程池 实现,map 用于存储键值对数据,map的value是 Cache 的内部类对象 Entity,Entity 包含 value 和该键值对的生命周期定时器 FutureCache 类对外只提供了 put(key, value)put(key, value, expire)get(key)get(key, class)remove(key)size()几个同步方法。

当添加键值对数据的时候,首先会调用remove()方法,清除掉原来相同 key 的数据,并取消对应的定时清除任务,然后添加新数据到 map 中,并且,如果设置了有效时间,则添加对应的定时清除任务到定时器线程池。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author 633805 LYH
 * @version V1.0
 * @description 简单内存缓存工具类
 * @create 2019-06-17 10:45
 * @since 1.7
 */
public class Cache {
    //键值对集合
    private final static Map<String, Entity> map = new HashMap<>();
    //定时器线程池,用于清除过期缓存
    private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    /*private final static  ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 3,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
            new ThreadPoolExecutor.DiscardOldestPolicy());*/

    /**
     * 添加缓存
     *
     * @param key  键
     * @param data 值
     */
    public synchronized static void put(String key, Object data) {
        Cache.put(key, data, 0);
    }

    /**
     * 添加缓存
     *
     * @param key    键
     * @param data   值
     * @param expire 过期时间,单位:毫秒, 0表示无限长
     */
    public synchronized static void put(String key, Object data, long expire) {
        //清除原键值对
        Cache.remove(key);
        //设置过期时间
        if (expire > 0) {
            Future future = executor.schedule(new Runnable() {
                @Override
                public void run() {
                    //过期后清除该键值对
                    synchronized (Cache.class) {
                        map.remove(key);
                    }
                }
            }, expire, TimeUnit.MILLISECONDS);
            map.put(key, new Entity(data, future));
        } else {
            //不设置过期时间
            map.put(key, new Entity(data, null));
        }
    }

    /**
     * 读取缓存
     *
     * @param key 键
     * @return
     */
    public synchronized static Object get(String key) {
        Entity entity = map.get(key);
        return entity == null ? null : entity.getValue();
    }

    /**
     * 读取缓存
     *
     * @param key 键
     *            * @param clazz 值类型
     * @return
     */
    public synchronized static <T> T get(String key, Class<T> clazz) {
        return clazz.cast(Cache.get(key));
    }

    /**
     * 清除缓存
     *
     * @param key
     * @return
     */
    public synchronized static Object remove(String key) {
        //清除原缓存数据
        Entity entity = map.remove(key);
        if (entity == null) {
            return null;
        }
        //清除原键值对定时器
        Future future = entity.getFuture();
        if (future != null) {
            future.cancel(true);
        }
        return entity.getValue();
    }

    /**
     * 查询当前缓存的键值对数量
     *
     * @return
     */
    public synchronized static int size() {
        return map.size();
    }

    /**
     * 缓存实体类
     */
    private static class Entity {
        //键值对的value
        private Object value;
        //定时器Future
        private Future future;

        public Entity(Object value, Future future) {
            this.value = value;
            this.future = future;
        }

        /**
         * 获取值
         *
         * @return
         */
        public Object getValue() {
            return value;
        }

        /**
         * 获取Future对象
         *
         * @return
         */
        public Future getFuture() {
            return future;
        }
    }
}

通过上面代码,简单的工具类就实现了,下面进行测试

/**
 * @author 633805 LYH
 * @version V1.0
 * @description 缓存工具类测试
 * @create 2019-06-17 11:00
 * @since 1.7
 */
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CacheTest {

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        String key = "id";
        //不设置过期时间
        System.out.println("***********不设置过期时间**********");
        Cache.put(key, 123);
        System.out.println("key:" + key + ", value:" + Cache.get(key));
        System.out.println("key:" + key + ", value:" + Cache.remove(key));
        System.out.println("key:" + key + ", value:" + Cache.get(key));

        //设置过期时间
        System.out.println("\n***********设置过期时间**********");
        Cache.put(key, "123456", 1000);
        System.out.println("key:" + key + ", value:" + Cache.get(key));
        Thread.sleep(2000);
        System.out.println("key:" + key + ", value:" + Cache.get(key));

        /******************并发性能测试************/
        System.out.println("\n***********并发性能测试************");
        //创建有10个线程的线程池,将1000000次操作分10次添加到线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future[] futures = new Future[10];
        /********添加********/
        {
            long start = System.currentTimeMillis();
            for (int j = 0; j < 10; j++) {
                futures[j] = executorService.submit(() -> {
                    for (int i = 0; i < 100000; i++) {
                        Cache.put(Thread.currentThread().getId() + key + i, i, 300000);
                    }
                });
            }
            //等待全部线程执行完成,打印执行时间
            for (Future future : futures) {
                future.get();
            }
            System.out.printf("添加耗时:%dms\n", System.currentTimeMillis() - start);
        }

        /********查询********/
        {
            long start = System.currentTimeMillis();
            for (int j = 0; j < 10; j++) {
                futures[j] = executorService.submit(() -> {
                    for (int i = 0; i < 100000; i++) {
                        Cache.get(Thread.currentThread().getId() + key + i);
                    }
                });
            }
            //等待全部线程执行完成,打印执行时间
            for (Future future : futures) {
                future.get();
            }
            System.out.printf("查询耗时:%dms\n", System.currentTimeMillis() - start);
        }

        System.out.println("当前缓存容量:" + Cache.size());
    }
}

 测试结果:

测试程序使用有10个线程的线程池来模拟并发,总共执行一百万次添加和查询操作,时间大约都在4秒多,表现还不错,每秒20万读写并发应该还是可以满足大多数高并发场景 

 

Guava Cache实现

Guava缓存值CacheBuilder介绍-参考 
Google -CachesExplained wiki 
缓存框架Guava Cache部分源码分析

package cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author 633805 LYH
 * @version V1.0
 * @description 对类的描述
 * @create 2019-06-17 14:01
 * @since 1.7
 */
@Slf4j
public abstract class SuperBaseGuavaCache<K, V> {

    /**
     * 缓存对象
     */
    private LoadingCache<K, V> cache;

    /**
     * 缓存最大容量,默认为10
     */
    protected Integer maximumsize = 1000000;

    /**
     * 缓存失效时间
     */
    protected Long duration = 10L;

    /**
     * 缓存失效单位,默认为5s
     */
    protected TimeUnit timeUnit = TimeUnit.SECONDS;

    /**
     * 返回loading cache (单列模式的)
     *
     * @return loadingCache<K, V>
     */
    private LoadingCache<K, V> getCache() {
        if (cache == null) {
            synchronized (SuperBaseGuavaCache.class) {
                if (cache == null) {
                    CacheBuilder<Object, Object> tempCache = null;
                    if (duration > 0 && timeUnit != null) {
                        tempCache = CacheBuilder.newBuilder().expireAfterWrite(duration, timeUnit);
                    }
                    //设置最大缓存大小
                    if (maximumsize > 0 ) {
                        tempCache.maximumSize(maximumsize);
                    }
                    //加载缓存
                    cache = tempCache.build(new CacheLoader<K, V>() {
                        //缓存不存在或者过期时调用
                        @Override
                        public V load(K key) throws Exception {
                            //不允许返回null值
                            V target = getLoadData(key) != null ? getLoadData(key): getLoadDataIfNull(key);
                            return target;

                        }
                    });
                }
            }
        }
        return cache;
    }

    /**
     * 返回加载到内存中的数据,一般从数据库中加载
     * @param key key值
     * @return V
     */
    abstract V getLoadData(K key);

    /**
     * 调用getLoadData返回null值时自定义加载到内存的值
     * @param key key值
     * @return V
     */
    abstract V getLoadDataIfNull(K key);

    /**
     * 清除缓存(可以批量清除,也可以清除全部)
     * @param keys 需要清除缓存的key值
     */
    public void batchInvalidate(List<K> keys) {
        if (keys != null) {
            getCache().invalidateAll(keys);
            log.info("批量清除缓存, keys为:{}", keys);
        } else {
            getCache().invalidateAll();
            log.info("清除了所有缓存");
        }
    }

    /**
     * 清除某个key的缓存
     */
    public void invalidateOne(K key) {
        getCache().invalidate(key);
        log.info("清除了guava cache 中的缓存, key为: {}", key);
    }

    /**
     * 写入缓存
     * @param key 键
     * @param value 键对应的值
     */
    public void putIntoCache(K key, V value) {
        getCache().put(key, value);
    }

    /**
     * 获取某个key对应的缓存
     * @param key
     * @return V
     */
    public V getCacheValue(K key) {
        V cacheValue = null;
        try {
            cacheValue = getCache().get(key);
        } catch (Throwable t) {
            log.error("获取guava cache中的缓存出错, {}", t);
            return null;
        }
        return cacheValue;

    }
}
package cache;

/**
 * @author 633805 LYH
 * @version V1.0
 * @description 对类的描述
 * @create 2019-06-17 15:16
 * @since 1.7
 */
public class GuavaCache extends SuperBaseGuavaCache<String, Object> {


    /**
     * 返回加载到内存中的数据,从redis中查找
     *
     * @param key key值
     * @return
     */
    @Override
    Object getLoadData(String key) {
        //redis获取然后存入本地缓存

        return null;
    }

    /**
     * 调用getLoadData返回null值时自定义加载到内存的值
     * @param key key值
     * @return
     */
    @Override
    Object getLoadDataIfNull(String key) {
        //数据库中查找然后进入redis然后进入本地缓存

        return null;
    }
}

 

package cache;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @author 633805 LYH
 * @version V1.0
 * @description 缓存工具类测试
 * @create 2019-06-17 11:00
 * @since 1.7
 */
public class GuavaCacheTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        GuavaCache guavaCache = new GuavaCache();
        String key = "id";
        //不设置过期时间
        System.out.println("***********不设置过期时间**********");
        guavaCache.putIntoCache(key, "123");
        System.out.println("key:" + key + ", value:" + guavaCache.getCacheValue(key));



        /******************并发性能测试************/
        System.out.println("\n***********并发性能测试************");
        //创建有10个线程的线程池,将1000000次操作分10次添加到线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future[] futures = new Future[10];
        /********添加********/
        {
            long start = System.currentTimeMillis();
            for (int j = 0; j < 10; j++) {
                futures[j] = executorService.submit(() -> {
                    for (int i = 0; i < 100000; i++) {
                        guavaCache.putIntoCache( Thread.currentThread().getId() + key + i, i);
                    }
                });
            }
            //等待全部线程执行完成,打印执行时间
            for (Future future : futures) {
                future.get();
            }
            System.out.printf("添加耗时:%dms\n", System.currentTimeMillis() - start);
        }

        /********查询********/
        {
            long start = System.currentTimeMillis();
            for (int j = 0; j < 10; j++) {
                futures[j] = executorService.submit(() -> {
                    for (int i = 0; i < 100000; i++) {
                        guavaCache.getCacheValue(Thread.currentThread().getId() + key + i);
                    }
                });
            }
            //等待全部线程执行完成,打印执行时间
            for (Future future : futures) {
                future.get();
            }
            System.out.printf("查询耗时:%dms\n", System.currentTimeMillis() - start);
        }

        System.out.println("当前缓存容量:" + guavaCache.maximumsize);
    }
}

想要空间换时间的方案可以采用第二种,关于两者选择及一些区别可以参考https://blog.csdn.net/zollty/article/details/86134550

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的基于HashMap实现的本地缓存工具类的示例: ```java import java.util.HashMap; import java.util.Map; public class LocalCache<K, V> { // 默认缓存容量 private static final int DEFAULT_CAPACITY = 1024; // 最大缓存容量 private static final int MAX_CAPACITY = 1024 * 1024; // 缓存容量 private int capacity; // 缓存数据 private Map<K, V> cache; public LocalCache() { this(DEFAULT_CAPACITY); } public LocalCache(int capacity) { if (capacity > MAX_CAPACITY) { throw new IllegalArgumentException("Capacity too large: " + capacity); } this.capacity = capacity; this.cache = new HashMap<>(capacity); } /** * 添加缓存数据 * * @param key 缓存键 * @param value 缓存值 */ public synchronized void put(K key, V value) { if (cache.size() >= capacity) { // 如果缓存达到容量上限,删除最老的一条数据 K oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey); } cache.put(key, value); } /** * 获取缓存数据 * * @param key 缓存键 * @return 缓存值 */ public synchronized V get(K key) { return cache.get(key); } /** * 删除缓存数据 * * @param key 缓存键 * @return 被删除的缓存值 */ public synchronized V remove(K key) { return cache.remove(key); } /** * 清空缓存数据 */ public synchronized void clear() { cache.clear(); } /** * 获取缓存大小 * * @return 缓存大小 */ public synchronized int size() { return cache.size(); } } ``` 这个工具类使用了HashMap来存储缓存数据,支持添加、获取、删除和清空缓存数据的操作,还支持设置缓存容量,并自动删除最老的一条数据来保持缓存容量的限制。当然,这只是一个简单的示例,实际应用中还需要考虑线程安全、缓存过期等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值