欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自己实现一个内存缓存

程序员文章站 2022-05-21 21:39:42
...

         最近了解了下缓存技术,主要分为内存缓存 和磁盘缓存,然后分布式里面有一些消息的推送,节点的一些处理。现在很多的用OSCache,EhCache 等等,资料很多,暂时就不多说了,我这里仅仅为了了解缓存框架的的知识,自己临时写一个缓存,后面可以实现AOP 和spring 结合。

 

 

实现功能:
1.数据都缓存到内存中
2.实现对每个元素Element的缓存时间进行控制
3.对每个缓存Cache 进行创建,以及销毁的管理
4.能对缓存存放个数进行管理
5.通过线程定时检查元素是否过期
6.可以定义缓存清除策略

 

实现思路:
1.Store 接口:定义常用的存取元素get,put的接口
2.AbstractStore:对接口基本方法进行实现
3.MemoryCache:对具体缓存的存取方法进行实现,并执行一定的逻辑控制
4.Cache:增加对缓存整体进行监听,销毁等操作
5.CacheManager:缓存管理器,负责管理缓存的容器

6.CacheConfiguration :一些缓存的配置属性
7.CheckManager:一些检查缓存内容的策略方法
8.EvictionType:枚举清空策略
9.CacheListener:缓存的监听,每次创建缓存就启动,然后定时检查缓存元素是否过期
10.Element: 元素的单元

 

package com.cache;

import java.util.Collection;


public interface Store {
	// 获得缓存名字
	public String getName();
	// 存放元素
	public Element put(Element e);
	public Collection<Element> putAll(Collection<Element> elements);
	// 获取元素
	public Element get(Object key);
	// 清除元素
	public void clear();
	// 移除元素
	public void remove(Object key);
	public void removeAll(Object[] keys);
	// 获得的元素长度
	public Integer size();
}

 

package com.cache;

import java.util.Map;

/**
 * 抽象类实现
 */
public abstract class AbstractStore implements Store{
	
	protected  Map<Object, Element> map;
	
	public AbstractStore(){}
	public AbstractStore(Map<Object, Element> map){
		this.map = map;
	}
	
	@Override
	public Element get(Object key) {
		Element e = map.get(key);
		return e;
	}
	
	public Map<Object, Element> getAll(){
		return map;
	}
	
	@Override
	public void clear() {
		 map.clear();
	}
	
	@Override
	public Element put(Element e) {
		return map.put(e.getKey(), e);
	}
	
	@Override
	public void remove(Object key) {
		map.remove(key);
	}

	@Override
	public Integer size() {
		return map.size();
	}
	
	@Override
	public void removeAll(Object[] keys) {
		for(int i =0;i<keys.length;i++){
			remove(keys[i]);
		}
	}
}

 

package com.cache;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 缓存的基本方法
 */
public class MemoryCache extends AbstractStore implements Store{
	
	private final CacheConfiguration configure;
	private final static Map<Object, Element> map = new HashMap<Object, Element>();
	private final CheckManager checkManager;
	
	
	// 暂时仅弄一个构造
	public MemoryCache(CacheConfiguration configure){
		super(map);
		this.configure = configure;
		this.checkManager = new CheckManager(configure, map);
	}
	
	@Override
	public String getName() {
		return configure.getCacheName();
	}

	@Override
	public  Collection<Element>  putAll(Collection<Element> elements) {
		if(elements == null){
			throw new NullPointerException("elements can't be null");
		}
		check(elements.size());
		for(Element e : elements){
			putElementStatus(e);
			super.put(e);
		}
		return elements;
	}
	
	@Override
	public synchronized Element put(Element e) {
		check(1);
		putElementStatus(e);
		return super.put(e);
	}
	
	
	// 使用一次之后刷新使用过期时间,以及使用次数
	// 并检查该元素是否过期
	public void changeElement(Element e){
		e.addHitCount();
		if(!configure.getEternal()){
			e.refreshLastAccessTime();
		}
	}
	
	// 如果eternal 为true,表示元素永不过期,默认忽略最小元素控制
	public void putElementStatus(Element e){
		if(!configure.getEternal() && ! e.getIsOpen()){
			e.setTimeToIdle(configure.getTimeToIdleSeconds());
			e.setTimeToLive(configure.getTimeToLiveSeconds());
		}else{
			e.setTimeToIdle(0);
			e.setTimeToLive(0);
		}
	}
	
	
	@Override
	public  Element get(Object key) {
		Element e = super.get(key);
		if(e != null){
			if(!e.isExpired()){
				changeElement(e);
			}else{
				synchronized (this) {
					remove(e.getKey());
					e = null;
				}
			}
		}
		return e;
	}
	
	
	
	// 检查元素 是否为空
	public boolean checkElement(Element e){
		if(e == null){
			throw new NullPointerException("Element can't be  null ");
		}
		if(e.getKey() == null){
			throw new NullPointerException("Element key can't be  null ");
		}
		return true;
	}

	@Override
	public synchronized void removeAll(Object[] keys) {
		super.removeAll(keys);
	}
	
	// 检查元素是否超过了
	public void check(int checkSize){
		if(checkSize <= 0){
			return;
		}
		Object[] keys = checkManager.checkConfigure(checkSize);
		if(keys !=null) {
			removeAll(keys);
		}
	}
}

 

 

package com.cache;

/**
 * 这是对缓存 级的控制判断
 */
public class Cache extends MemoryCache{
	
	private CacheConfiguration configure;
	private   CacheListener listener;
	
	public Cache(CacheConfiguration configure) {
		super(configure);
		this.configure = configure;
		if(!configure.getEternal()  &&  configure.getIsNeedCacheCheckListener()){
			listener = new CacheListener(this);
			listener.start();
		}
	}
	public CacheConfiguration getConfigure() {
		return configure;
	}
	
	// 销毁
	public void destory(){
		try{
			super.clear();
			if(listener != null){
				listener.interrupt();
				listener.stop();
				listener = null;
			}
		}catch (Exception e) {
		}
		
	}
}

 

 

 

package com.cache;

import java.util.HashMap;
import java.util.Map;

/**
 * 缓存管理类
 */
public class CacheManager {
	// 默认单例
	private CacheManager(){}
	private static class  Singleton{
		private static CacheManager instance = new CacheManager();
	}
	public static CacheManager getInstance() {
		return Singleton.instance;
	}
	
	public static Map<String, Cache> MAP_NAMES_CACHE = new HashMap<String, Cache>();
	
	// 存放 取出 缓存对象
	public Cache getCache(String CacheName){
		Cache cache = MAP_NAMES_CACHE.get(CacheName);
		return cache;
	}
	
	public void putCache(Cache cache){
		if(cache != null  && !MAP_NAMES_CACHE.containsKey(cache.getName())){
			MAP_NAMES_CACHE.put(cache.getName(), cache);
		}
	}
	
	// 移除
	public void remove(String cacheName){
		Cache c = MAP_NAMES_CACHE.remove(cacheName);
		c.destory();
	}
	
	// 关闭所有缓存
	public void shutDown(){
		removeAllCaches();
		MAP_NAMES_CACHE.clear();
	}
	
	// 移除所有
	public void removeAllCaches(){
		String [] cacheNames = getCacheNames();
		for(String cacheName : cacheNames){
			remove(cacheName);
		}
	}
	
	// 获得名字
	public String[] getCacheNames(){
        return MAP_NAMES_CACHE.keySet().toArray(new String[0]);
    }
	
}

 

package com.cache;
/**
 * 
 * 这些属性来自于ehcache 
 * 属于缓存级别的 一些控制
 */
public class CacheConfiguration {
	// 缓存的名字
	private String cacheName;
	// 是否需要缓存 循环检查
	private Boolean isNeedCacheCheckListener = false;
	public Boolean getIsNeedCacheCheckListener() {
		return isNeedCacheCheckListener;
	}
	public void setIsNeedCacheCheckListener(Boolean isNeedCacheCheckListener) {
		this.isNeedCacheCheckListener = isNeedCacheCheckListener;
	}
	// 内存最大缓存对象数
	private Integer maxElementsInMemory;
	
	// 缓存元素是否永久有效,一旦设置true ,失效时间 将不起作用,默认false
	private Boolean eternal = false;
	
	// 设置缓存在失效前的允许闲置时间。仅当缓存不是永久有效时使用(timeToLiveSeconds != 0)
	// 可选属性,默认值是0,也就是可闲置时间无穷大。
	private Integer timeToIdleSeconds = 0;
	
	// 设置缓存长允许存活时间,最大时间介于创建时间和失效时间之间.
	// 仅当缓存不是永久有效时使用,默认值是0,也就是缓存存活时间无穷大。
	private Integer timeToLiveSeconds = 0;
	// 对象检测线程运行的时间间隔。表示对象状态的线程多长时间运行一次
	// 这里暂时用来对内存对象的检查
	private Integer diskExpiryThreadIntervalSeconds = 120;
	
	// 如果缓存满了,执行清空策略
	// 可选FIFO,LFU 这里要用枚举类型
	// FIFO :先进先出
	// LFU:最少使用,一直以来最少被使用的,缓存缓存有一个hit属性,清除hit最小的
	// LRU:最近最少使用,缓存元素有个时间戳,当缓存容量满了,而又需要腾出新地方
	// 来缓存的时候,那么现有的缓存缓存中时间戳离当前时间最远的缓存将被清除缓存
	private String memoryStoreEvictionPolicy = EvictionType.LRU.name();
	
	
	// 暂时未用
	// 当缓存数量达到最大值时,允许将缓存写入到磁盘
	private Boolean overflowToDisk = false;
	// 磁盘中最大的缓存对象数,若是0表示无穷大
	private Integer maxElementsOnDisk = 0;
	// 是否在磁盘上持久化,默认false
	private Boolean diskPersistent = false;

	public CacheConfiguration() {
	}
	public CacheConfiguration(String cacheName,Integer maxElementsInMemory,
			Boolean eternal,Integer timeToIdleSeconds,Integer timeToLiveSeconds,
			Integer diskExpiryThreadIntervalSeconds) {
		this.cacheName = cacheName;
		this.maxElementsInMemory = maxElementsInMemory;
		this.eternal = eternal;
		this.timeToIdleSeconds = timeToIdleSeconds;
		this.timeToLiveSeconds = timeToLiveSeconds;
		this.diskExpiryThreadIntervalSeconds = diskExpiryThreadIntervalSeconds;
	}
	
	public String getCacheName() {
		return cacheName;
	}
	public void setCacheName(String cacheName) {
		this.cacheName = cacheName;
	}
	public Integer getMaxElementsInMemory() {
		return maxElementsInMemory;
	}
	public void setMaxElementsInMemory(Integer maxElementsInMemory) {
		this.maxElementsInMemory = maxElementsInMemory;
	}
	public Boolean getEternal() {
		return eternal;
	}
	public void setEternal(Boolean eternal) {
		this.eternal = eternal;
	}
	public Integer getTimeToIdleSeconds() {
		return timeToIdleSeconds;
	}
	public void setTimeToIdleSeconds(Integer timeToIdleSeconds) {
		this.timeToIdleSeconds = timeToIdleSeconds;
	}
	public Integer getTimeToLiveSeconds() {
		return timeToLiveSeconds;
	}
	public void setTimeToLiveSeconds(Integer timeToLiveSeconds) {
		this.timeToLiveSeconds = timeToLiveSeconds;
	}
	public Boolean getOverflowToDisk() {
		return overflowToDisk;
	}
	public void setOverflowToDisk(Boolean overflowToDisk) {
		this.overflowToDisk = overflowToDisk;
	}
	public Integer getMaxElementsOnDisk() {
		return maxElementsOnDisk;
	}
	public void setMaxElementsOnDisk(Integer maxElementsOnDisk) {
		this.maxElementsOnDisk = maxElementsOnDisk;
	}
	public Boolean getDiskPersistent() {
		return diskPersistent;
	}
	public void setDiskPersistent(Boolean diskPersistent) {
		this.diskPersistent = diskPersistent;
	}
	public Integer getDiskExpiryThreadIntervalSeconds() {
		return diskExpiryThreadIntervalSeconds;
	}
	public void setDiskExpiryThreadIntervalSeconds(
			Integer diskExpiryThreadIntervalSeconds) {
		this.diskExpiryThreadIntervalSeconds = diskExpiryThreadIntervalSeconds;
	}
	public String getMemoryStoreEvictionPolicy() {
		return memoryStoreEvictionPolicy;
	}
	public void setMemoryStoreEvictionPolicy(String memoryStoreEvictionPolicy) {
		this.memoryStoreEvictionPolicy = memoryStoreEvictionPolicy;
	}
	
	
	
}

 

 

package com.cache;

import java.io.Serializable;

/**
 * 缓存元素,所对应的属性
 */
@SuppressWarnings("serial")
public class Element implements Serializable {
	static final long ONE_SECOND = 1000L;
	private Object key;
	private Object value;
	// 使用次数
	private volatile long hitCount = 0;
	// 这些属性是单个元素的属性控制
	// 是否使用单独元素控制
	private Boolean isOpen = false;
	// 从创建时间开始后,还能存活时间,0 表示一直存活,超时时间=timeToLive + CreationTime
	private volatile int timeToLive = 0;
	// 从最近即(min(CreationTime,LastAccessTime)) 后还剩余的时间
	private volatile int timeToIdle = 0;
	// 创建时间
	private transient long creationTime;
	// 最后一次使用的时间
	private transient long lastAccessTime;
	// 最后更新时间
	private volatile long lastUpdateTime;
	// 表示是否使用cache 级别的控制,还是元素级别的控制,这里暂时不用
	private volatile boolean cacheDefaultLifespan = true;
	
	public Element(final Object key, final Object value){
		init(key,value);
	}
	
	public Element(final Object key, final Object value,Boolean isOpen){
		init(key, value);
		this.isOpen = isOpen;
	}
	
	private void init(final Object key, final Object value){
		this.key = key;
		this.value = value;
		this.creationTime = System.currentTimeMillis();
		this.lastAccessTime = System.currentTimeMillis();
	}
	
	
	public Object getKey() {
		return key;
	}
	public void setKey(Object key) {
		this.key = key;
	}
	public Object getValue() {
		return value;
	}
	public long getHitCount() {
		return hitCount;
	}
	public void setHitCount(long hitCount) {
		this.hitCount = hitCount;
	}
	public void addHitCount(){
		hitCount += 1;
	}

	public int getTimeToLive() {
		return timeToLive;
	}


	public void setTimeToLive(int timeToLive) {
		this.timeToLive = timeToLive;
	}


	public int getTimeToIdle() {
		return timeToIdle;
	}


	public void setTimeToIdle(int timeToIdle) {
		this.timeToIdle = timeToIdle;
	}
	public long getCreationTime() {
		return creationTime;
	}
	public void setCreationTime(long creationTime) {
		this.creationTime = creationTime;
	}
	public long getLastAccessTime() {
		return lastAccessTime;
	}
	
	public void setLastAccessTime(long lastAccessTime) {
		this.lastAccessTime = lastAccessTime;
	}
	public long getLastUpdateTime() {
		return lastUpdateTime;
	}
	public void setLastUpdateTime(long lastUpdateTime) {
		this.lastUpdateTime = lastUpdateTime;
	}


	public boolean isCacheDefaultLifespan() {
		return cacheDefaultLifespan;
	}


	public void setCacheDefaultLifespan(boolean cacheDefaultLifespan) {
		this.cacheDefaultLifespan = cacheDefaultLifespan;
	}


	public void setValue(Object value) {
		this.value = value;
	}
	
	

	 public Boolean getIsOpen() {
		return isOpen;
	}

	public void setIsOpen(Boolean isOpen) {
		this.isOpen = isOpen;
	}

	/**
	  * 判断元素 是否过期
	  * @return
	  */
	 public boolean isExpired() {
	        if (isEternal()) {
	            return false;
	        }
	        // 获得过期时间
	        long expirationTime = getExpirationTime();
	        long now = System.currentTimeMillis();
	        return now > expirationTime;
	 }
	 // 是否是不会过期
	 public boolean isEternal() {
	        return (0 == timeToIdle) && (0 == timeToLive);
	 }
	 
	 // 计算过期时间
	 public long getExpirationTime() {
	        if (isEternal()) {
	            return Long.MAX_VALUE;
	        }
	        // 存活时间
	        long expirationTime = 0;
	        long ttlExpiry = creationTime + getTimeToLive() * ONE_SECOND;
	        // 到期时间
	        long mostRecentTime = Math.max(creationTime, lastAccessTime);
	        long ttiExpiry = mostRecentTime + getTimeToIdle() * ONE_SECOND;
	        // 如果仅仅设置了timeToLive,那么时间以 timeToLive的计算为准
	        if (getTimeToLive() != 0 && (getTimeToIdle() == 0 || lastAccessTime == 0)) {
	            expirationTime = ttlExpiry;
	        } else if (getTimeToLive() == 0) {
	        	// 如果仅仅设置了 timeToIdle,那么时间以timeToIdle 的计算为准
	            expirationTime = ttiExpiry;
	        } else {
	        	// 如果两种都设置了,那么取小的一个为准
	            expirationTime = Math.min(ttlExpiry, ttiExpiry);
	        }
	        return expirationTime;
	    }
	 
	 // 刷新最后一次使用时间
	 public void refreshLastAccessTime(){
		 lastAccessTime = System.currentTimeMillis();
	 }
	
}

 

package com.cache;

import java.util.Iterator;
import java.util.Map;
/**
 * 检查的的一些方式
 * @author Administrator
 *
 */
public class CheckManager {

	protected CacheConfiguration configure;
	protected Map<Object, Element> map;
	
	public CheckManager(CacheConfiguration configure,Map<Object, Element> map) {
		this.map = map;
		this.configure = configure;
	}
	
	// 添加检查元素是否已经到达最大值,或者已经过期
	public Object[]  checkConfigure(int elementSize){
		int removeSize = map.size()+elementSize - configure.getMaxElementsInMemory();
		// 判断缓存是否已满
		if(removeSize > 0){
			// 按规则删除元素,这里不写磁盘
			if(!configure.getDiskPersistent()){
				return removeElementByEvictionType(removeSize);
			}
		}
		return null;
	}
	
	// 根据方式移除
	public Object[] removeElementByEvictionType(int removeSize){
		if(configure.getMemoryStoreEvictionPolicy().equals(EvictionType.LRU.name())){
			return removeElementByLRU(removeSize);
		}
		return null;
	}
	
	
	
	// 暂时默认根据最少使用次数进行删除
	private Object[] removeElementByLRU(int removeSize){
		Object keys[] = new Object[removeSize];
		long hits[] = new long[removeSize];
		Iterator<?> it = map.keySet().iterator();
		// 找出hit值最小的 removeSize 个元素
		int index = 0;
		while(it.hasNext()){
			Object key =  it.next();
			Element e = map.get(key);
			long hit = e.getHitCount();
			if(index < removeSize){
				hits[index] = hit;
				keys[index] = key;
				index ++;
			}else{
				long pos = getMinIndex(hits, hit);
				if(pos >= 0){
					keys[(int) pos] = key;
				}
			}
		}
		return keys;
	}
	
	private long getMinIndex(long hits[],long hit){
		long pos = -1;
		for(int i = 0;i<hits.length;i++){
			if(hits[i] > hit){
				hits[i] = hit;
				pos = i;;
			}
		}
		return pos;
	}

	
	
}

 

package com.cache;
/**
 * 几种删除策略
 */
public enum EvictionType {
	 LRU,LFU,FIFO
}

 

package com.cache;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 循环检查元素
 * @author Administrator
 *
 */
public class CacheListener extends Thread{
	
	private Cache cache;
	
	private volatile boolean stop = false;
	private volatile long ONE_SECOND = 1000;
	
	public boolean isStop() {
		return stop;
	}
	public void setStop(boolean stop) {
		this.stop = stop;
	}
	public CacheListener(Cache cache) {
		this.cache = cache;
	}
	
	@Override
	public void run() {
		long time = cache.getConfigure().getDiskExpiryThreadIntervalSeconds();
		try {
			while(!stop){
				sleep(time*ONE_SECOND);
				threadCheckElement();
			}
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
	}
	
	public void destory(){
		ONE_SECOND = 0;
		stop = true;
	}
	
	
	public void threadCheckElement(){
		List<Object> keys = new ArrayList<Object>();
		Map<Object, Element> map  = cache.getAll();
		if(map != null && map.size() > 0){
			for(Entry<Object, Element> e0: map.entrySet()){
				Element e = e0.getValue();
				if(e != null && e.isExpired()){
					keys.add(e0.getKey());
				}
			}
		}
		cache.removeAll(keys.toArray());
	}
	
	

}

 

package com.test;

import com.cache.Cache;
import com.cache.CacheConfiguration;
import com.cache.CacheManager;
import com.cache.Element;



public class Test {

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		CacheManager cm = CacheManager.getInstance();
		Cache c1 = new Cache(getConfigure());
		// 最大放5个
		putTestE1(c1);
		cm.putCache(c1);
		
		// 只有2 的hit 值最小,因此超过了就被移除了 null
		System.out.println(c1.get(2));
		// 想存放6了,实际数据只有5个
		System.out.println("总数:"+c1.size());
		// 休息3秒,然后使用
		Thread.sleep(1000*3);
		System.out.println("刷新:"+c1.get(1));
		// 然后继续休息
		Thread.sleep(1000*3);
		System.out.println("使用着的元素:"+c1.size());
		Thread.sleep(1000*15);
		System.out.println("时间太久,全部过期"+c1.size());
		
		//cm.shutDown();
		
	}
	
	public static CacheConfiguration getConfigure(){
		CacheConfiguration c = new CacheConfiguration();
		c.setCacheName("Test1");
		// 最多存放5个元素
		c.setMaxElementsInMemory(5);
		// 假设5秒不用就过期,这两个时间一般默认选小的一个执行,最长时间是 存活的总时间
		c.setTimeToIdleSeconds(5);
		// 假设最长能存活115秒
		c.setTimeToLiveSeconds(115);
		// 6秒 检查一次过期
		c.setDiskExpiryThreadIntervalSeconds(6);
		return c;
	}
	
	public static void putTestE1(Cache c1){
		c1.put(new Element(1, 2));
		c1.get(1);
		c1.put(new Element(2, 2));
		//c1.get(2);
		c1.put(new Element(3, 2));
		c1.get(3);
		c1.put(new Element(4, 2));
		c1.get(4);
		c1.put(new Element(5, 2));
		c1.get(5);
		c1.put(new Element(6, 2));
	}
	
	

}

 

小结:

         1.这里是看了一些Ehcache 的源码,加上自己的思路写的,仅做第一次尝试之用

         2.关于磁盘缓存 和 分布式 的东西这里是没有的,包括异常各种处理,这里都是没有的

         3.关于缓存管理,这里每个cache 都有一个线程,是需要改进的,比较消耗资源

         4.测试代码,我测试了一部分,会有遗漏的,留点有兴趣的去搞吧。

         5.有问题请多指点,代码,和ehcache 已经在里面的,可以参考。