适用于复杂业务提高性能。
适用场景:
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;
}
}
}
}
压缩的情况下结果: