内存级缓存ConcurrentSkipListMap实现方式

适用于复杂业务提高性能。

适用场景:

1.外部传输一个200以上属性的JSON,我们只需要在一系列负责业务后取其中50个属性使用,即可把初始的JSON字符串压缩后放入Map,给一个过期时间,等后续业务使用时直接从Map获取。

2.系统中的一些常量、字典、等都可以在程序初始化时放入。使用时直接获取。比Redis更轻量级。

代码一共四个类:

一、CacheEntity 类

import java.io.Serializable;
import lombok.Builder;
import lombok.Data;

/**
 * <p><b>CacheEntity Description:</b> 内存级缓存实体类,必须有过期时间,禁止长期有效的数据出现;</p>
 * @author douzi
 * <b>DATE</b> 2019年7月15日 下午3:47:28
 */
@Data
@Builder
public class CacheEntity implements Serializable {
	/**
	 * <b>Fields</b> serialVersionUID :
	 */
	private static final long serialVersionUID = 7449985759453960984L;

	/**
	 * <b>Fields</b> key : 唯一键
	 */
	private String key;
	
	/**
	 *  value : 值
	 */
	private String value;
	
	/**
	 * <b>Fields</b> timestamp : 缓存的时候存的时间戳,用来计算该元素是否过期
	 */
	private Long timestamp;
	
	/**
	 * <b>Fields</b> expire : 有效期 秒 0为无限
	 */
	private int expire;
	
	/**
	 * <p><b>Title:</b> remainTime</p>
	 * <p><b>Description:</b> 获取剩余时间(秒)</p>
	 * @author douzi
	 * @return
	 */
	public int remainTime() {
		if (this.expire == 0) {
			return this.expire;
		}
		return this.expire - getTime();
	}
	
	/**
	 * <p><b>Title:</b> getTime</p>
	 * <p><b>Description:</b> 获取当前时间和缓存时间戳差值(秒) </p>
	 * @author douzi
	 * @return
	 */
	private int getTime() {
		if (this.expire == 0) {
			return this.expire;
		}
		Long current = System.currentTimeMillis();
		Long value = current - this.timestamp;
		return (int) (value / 1000) + 1;
	}
 
	/**
	 * <p><b>Title:</b> isExpire</p>
	 * <p><b>Description:</b> 判断缓存是否到期 false:不到期 true:到期</p>
	 * @author douzi
	 * @return
	 */
	public boolean isExpire() {
		if (this.expire == 0) {
			return false;
		}
//		log.info(getTime() + " " + expire);
		if (getTime() > this.expire) {
			return true;
		}
		return false;
	}
}

 

二、MemoryCache类

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;

import com.wondertek.oes.bc.common.utils.GZIPUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * <p><b>MemoryCache Description:</b> 内存级缓存类,自带压缩功能、自动清除过期时间功能</p>
 * @author douzi
 * <b>DATE</b> 2019年7月15日 下午3:17:04
 */
@Slf4j
public class MemoryCache {
	/** 饿汉式单例 */
//	private static final Map<String, CacheEntity> cache = new ConcurrentHashMap<String, CacheEntity>();
//	private static final Map<String, CacheEntity> cache = new ConcurrentSkipListMap<String, CacheEntity>();
	/** Initialization Demand Holder (IoDH) 技术,单例类中增加一个静态(static)内部类,既可以实现延迟加载,又可以保证线程安全,不影响系统性能 */
	private static class HolderClass {
		//跳表实现 类似redis数据结构,空间换时间加快速度
		private static final Map<String, CacheEntity> cache = new ConcurrentSkipListMap<String, CacheEntity>();
	}
	
	private MemoryCache() {
	}
	
    /**
     * 清理过期缓存是否在运行
     */
	public static volatile Boolean CLEAN_THREAD_IS_RUN = false;
    
    /**
     * 清理缓存间隔时间以及默认过期时间 秒
     */
    static final int DEFALUT_EXPIRE = 60;
    
    /**
     * 是否压缩
     */
    public static volatile Boolean IS_ZIP = true;
    
    /**
     * <p><b>Title:</b> clear</p>
     * <p><b>Description:</b> 清空全部缓存,项目启动时使用或实际业务中不建议使用</p>
     * @author douzi
     */
    public static void clearAll() {
        HolderClass.cache.clear();
    }
    
    /**
     * <p><b>Title:</b> getAll</p>
     * <p><b>Description:</b> 获取所有数据</p>
     * @author douzi
     * @return
     */
    public static Map<String, CacheEntity> getAll() {
    	return HolderClass.cache;
    }
    
    /**
     * <p><b>Title:</b> size</p>
     * <p><b>Description:</b> 获取缓存大小</p>
     * @author douzi
     * @return
     */
    public static int size() {
    	return HolderClass.cache.size();
    }
    
    /**
     * <p><b>Title:</b> get</p>
     * <p><b>Description:</b> 获取对象</p>
     * @author douzi
     * @param key 键
     * @return
     */
    public static String get(String key) {
    	if (checkCache(key)) {
    		CacheEntity entity = HolderClass.cache.get(key);
        	if (IS_ZIP) {
        		return GZIPUtils.uncompress(entity.getValue());
        	}
    		return entity.getValue();
    	}
    	return null;
    }
    
    /**
     * <p><b>Title:</b> checkCache</p>
     * <p><b>Description:</b> 判断缓存在不在,过没过期</p>
     * @author douzi
     * @param key
     * @return
     */
    public static boolean checkCache(String key) {
    	CacheEntity entity = HolderClass.cache.get(key);
        if (entity == null) {
            return false;
        }
        if (entity.isExpire()) {
            return false;
        }
        return true;
    }
    
    /**
     * <p><b>Title:</b> removeCache</p>
     * <p><b>Description:</b> 清除对应的对象</p>
     * @author douzi
     * @param key 键
     */
	public static void removeCache(String key){
		log.info("remove key:" + key);
		HolderClass.cache.remove(key);
	}
	
	/**
	 * <p><b>Title:</b> put</p>
	 * <p><b>Description:</b> 放置缓存</p>
	 * @author douzi
	 * @param key 键
	 * @param json 值
	 */
	public static void put(String key, String json) {
		put(key, json, DEFALUT_EXPIRE - 1);
	}
	
	/**
	 * <p><b>Title:</b> put</p>
	 * <p><b>Description:</b> 放置缓存</p>
	 * @author douzi
	 * @param key 键
	 * @param json 值
	 * @param expire 过期时间(秒) 0为永久
	 */
	public static void put(String key, String json, int expire) {
		if (IS_ZIP) {
			json = GZIPUtils.compress(json);
    	        }
		CacheEntity entity = CacheEntity.builder()	     .key(key).value(json).timestamp(System.currentTimeMillis()).expire(expire)
.build();
		HolderClass.cache.put(key, entity);
		startCleanThread();
		log.info("put json to cache key:" + key + " expire:" + expire + " size: " + json.length());
	}
	
	/**
	 * <p><b>Title:</b> clearExpireEntity</p>
	 * <p><b>Description:</b> 清除所有的过期对象</p>
	 * @author douzi
	 */
	public static void clearExpireEntity() {
        List<String> deleteKeyList = new LinkedList<>();
        for(Map.Entry<String, CacheEntity> entry : HolderClass.cache.entrySet()) {
            if (entry.getValue().isExpire()) {
            	deleteKeyList.add(entry.getKey());
            }
        }
        
        for (String deleteKey : deleteKeyList) {
        	removeCache(deleteKey);
		}
		log.info("delete cache count is :" + deleteKeyList.size());
	}
	
	/**
	 * <p><b>Title:</b> startCleanThread</p>
	 * <p><b>Description:</b> 开启清理过期缓存的线程</p>
	 * @author douzi
	 */
    private static void startCleanThread() {
        if (!CLEAN_THREAD_IS_RUN) {
	        CleanTimeOutThread cleanTimeOutThread = new CleanTimeOutThread();
	        Thread thread = new Thread(cleanTimeOutThread, "memory-cache-clean");
	        /** 设置为后台守护线程 */
	        thread.setDaemon(true);
	        thread.start();
        }
    }
}

三、CleanTimeOutThread 类

import lombok.extern.slf4j.Slf4j;

/**
 * <p><b>CleanTimeOutThread Description:</b> 清除内存缓存过期数据</p>
 * @author douzi
 * <b>DATE</b> 2019年7月15日 下午4:58:11
 */
@Slf4j
public class CleanTimeOutThread implements Runnable {

	@Override
	public void run() {
		log.info("===MemoryCache clean thread run===");
		MemoryCache.CLEAN_THREAD_IS_RUN = true;
		while(true) {
			try {
                Thread.sleep(MemoryCache.DEFALUT_EXPIRE * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.error("===MemoryCache clean thread down===");
                MemoryCache.CLEAN_THREAD_IS_RUN = false;
                Thread.currentThread().interrupt();
            }
			MemoryCache.clearExpireEntity();
	    }
	}

}

 

四、压缩类是网上拷贝 GZIPUtils 类

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import cn.hutool.core.io.IoUtil;

/**
 * <p><b>GZIPUtils Description:</b> 压缩解压缩类</p>
 * @author douzi
 * <b>DATE</b> 2019年7月15日 下午11:26:04
 */
public class GZIPUtils {
	/**
     * 使用gzip进行压缩
     */
    @SuppressWarnings("restriction")
	public static String compress(String primStr) {
        if (primStr == null || primStr.length() == 0) {
            return primStr;
        }
        //不需要关流
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip = null;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(primStr.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IoUtil.close(gzip);
        }
        return new sun.misc.BASE64Encoder().encode(out.toByteArray());
    }
 
    /**
     * 使用gzip进行解压缩
     */
    @SuppressWarnings("restriction")
	public static String uncompress(String compressedStr) {
        if (compressedStr == null) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = null;
        GZIPInputStream ginzip = null;
        byte[] compressed = null;
        String decompressed = null;
        try {
            compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);
            in = new ByteArrayInputStream(compressed);
            ginzip = new GZIPInputStream(in);
 
            byte[] buffer = new byte[1024];
            int offset = -1;
            while ((offset = ginzip.read(buffer)) != -1) {
                out.write(buffer, 0, offset);
            }
            decompressed = out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IoUtil.close(ginzip);
            IoUtil.close(in);
            IoUtil.close(out);
        }
        return decompressed;
    }
}

 

五、测试类

import org.apache.commons.lang3.time.StopWatch;
import com.wondertek.oes.bc.common.cache.MemoryCache;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MemoryCacheTest {
	
	public static void main(String[] args) {
		StopWatch sw = new StopWatch();
		sw.start();
		String aa = "douzi1111";
		for (int i = 0; i < 200; i++) {
			aa += " douzi1111 " + i;
		}
		//是否打开压缩
//		MemoryCache.IS_ZIP = false;
        for (int i = 0; i < 2000; i ++ ) {
			MemoryCache.put("douzi" + i, aa);
        }
		sw.stop();
		log.info("size:" + MemoryCache.size() + " " + aa.length()  + " 时长:"+ sw.getTime()+ " douzi: " + MemoryCache.get("douzi1"));
		while (true) {
			try {
				Thread.sleep(10000); //10秒获取一次
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			log.info("size1:" + MemoryCache.size() + " douzi: " + MemoryCache.get("douzi1"));
			if(!MemoryCache.checkCache("douzi1")) {
				break;
			}
		}
	}
}

压缩的情况下结果:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窦再兴

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值