1. 程式人生 > >幾種常見型別Cache的實現

幾種常見型別Cache的實現

        總述:Cache的實現有多種多樣,基本上各個java開源框架都有自己的實現,而使用的最多的三種Cache型別是:FIFO(先入先出)、Random(隨機)和LRU(最近最少使用)。上述分類主要是指Cache在儲存已滿時拋棄已有資料來快取新新增的資料所選取的策略。Cache用來快取資料的資料結構大多數是採用HashMap,以便快速存取。有些Cache的實現還設定有物件的快取時間等,以期達到更細緻的控制。

        在後面,提供了一個Cache的簡略實現版本,抽象出一個簡單的Cache介面,一個抽象的AbstractCache類,和上述三種類型Cache的具體實現,其中對於FIFO型別的Cache,提供了Array和List兩種實現方式,相比較而言,List的效率更高。而LRU型別的Cache則是在List型FIFO的Cache基礎之上再行封裝的。時間複雜度上,Cache中快取資料的讀取主要依賴於HashMap的讀取效率;寫入快取資料時,FIFO型別的List實現方式和Random型別為O(1),FIFO型別的Array實現方式為O(n),LRU型別最壞的時間複雜度為O(n)。空間複雜度上,LRU>list方式的FIFO>Array方式的FIFO=Random,不過List形式在記憶體分配上比Array形式更靈活。

Cache介面:

package com.lee.java.learning.cache;

/**
 * An interface defining the basic functions of a cache.
 * This cache can store the element by the key.
 * and also it can read the element from the cache by special key.
 * Except that, It provide methods to Get the size and capacity of
 * the cache, and judge a element exists in the cache or not.
 *
 * @version 1.0
 * @author lili06
 *
 */
public interface Cache {

	/**
	 * Add a element into cache by special key
	 * @param key
	 * @param value
	 */
	public void addElement(Object key, Object value);

	/**
	 * Read the element from the cache by special key
	 * @param key
	 * @return
	 */
	public Object getElement(Object key);

	/**
	 * If a element attached with the special key has been cached,
	 * this method return true, otherwise false.
	 * @param key
	 * @return
	 */
	public boolean isExist(Object key);

	/**
	 * Return the number of elements have been cached in the cache. not to be confused with
	 * the {@link #capacity()} which returns the number
	 * of elements that can be held in the cache at one time.
	 * @return the number of value object cached at one time
	 */
	public int size();

	/**
	 * Returns the maximum number of elements that can be cached at one time.
	 * <p>
	 * @return The maximum number of elements that can be cached at one time.
	 */
	public int capacity();

	/**
	 * Clear all the  cached elements.
	 */
	public void clear();

}

AbstractCache抽象類:

package com.lee.java.learning.cache;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;

public abstract class AbstractCache implements Cache, Serializable {

	private static final long serialVersionUID = 2046841211971587188L;

	public static final int DEFAULT_CAPACITY = 20;

	int size;
	int capacity;
	HashMap cacheMap;

	AbstractCache(int capacity) {
		size = 0;
		if(capacity <= 0) {
			throw new RuntimeException("the cache capacity must be a positive integer...");
		}
		this.capacity = capacity;
		cacheMap = new HashMap(capacity);
	}

	public abstract void addElement(Object key, Object value);

	public synchronized Object getElement(Object key) {
		Object obj = null;
		obj = cacheMap.get(key);
		if(obj != null) {
			return ((CacheEntity)obj).value;
		}

		return null;
	}

	public synchronized void clear() {
		cacheMap.clear();
		size = 0;
	}

	public synchronized boolean isExist(Object key) {
		if(getElement(key) != null) {
			return true;
		}

		return false;
	}

	public final synchronized int size() {
		return size;
	}

	public final synchronized int capacity() {
		return capacity;
	}

	public final synchronized boolean isFull() {
		return size == capacity;
	}

	static class CacheEntity {
		Object key;
		Object value;

		CacheEntity() {
			key = null;
			value = null;
		}
	}

}

CacheRandom實現類:

package com.lee.java.learning.cache;

import java.util.Random;

public class CacheRandom extends AbstractCache {

	private static final long serialVersionUID = -3906908597773607461L;

	private Random random;
	private RandomCacheEntity[] cache;

	public CacheRandom() {
		this(AbstractCache.DEFAULT_CAPACITY);
	}

	public CacheRandom(int capacity) {
		super(capacity);
		random = new Random(System.currentTimeMillis());
		cache = new RandomCacheEntity[capacity];
		for(int i=0; i<capacity; i++) {
			cache[i] = new RandomCacheEntity(i);
		}
	}

	@Override
	public synchronized void addElement(Object key, Object value) {
		int index = 0;
		Object obj = null;
		obj = cacheMap.get(key);

		if(obj != null) {
			RandomCacheEntity entity = (RandomCacheEntity)obj;
			entity.value = value;

			return;
		}

		if(!isFull()) {
			index = size;
			size++;
		}else {
			index = random.nextInt(capacity);
			cacheMap.remove(cache[index].key);
		}

		cache[index].key = key;
		cache[index].value = value;
		cacheMap.put(key, cache[index]);
	}

	@Override
	public synchronized void clear() {
		super.clear();
		for(int i=0; i<capacity; i++) {
			cache[i] = new RandomCacheEntity(i);
		}
	}

	static class RandomCacheEntity extends CacheEntity {
		int index;

		RandomCacheEntity(int index) {
			this.index = index;
		}
	}

}

ArrayCacheFIFO實現類:

package com.lee.java.learning.cache;

public class ArrayCacheFIFO extends AbstractCache {

	private static final long serialVersionUID = -4299736285228473989L;

	private int current = 0;
	private ArrayCacheEntity[] cache;

	public ArrayCacheFIFO() {
		this(AbstractCache.DEFAULT_CAPACITY);
	}

	public ArrayCacheFIFO(int capacity) {
		super(capacity);
		cache = new ArrayCacheEntity[capacity];
		for(int i=0; i<capacity; i++) {
			cache[i] = new ArrayCacheEntity(i);
		}
	}

	@Override
	public synchronized void addElement(Object key, Object value) {
		int index = current;
		Object obj = null;
		obj = getElement(key);

		if(obj != null) {
			ArrayCacheEntity entity = (ArrayCacheEntity)obj;
			updateOrder(entity, value);
			return;
		}

		if(!isFull()) {
			index = size;
			size++;
		}else {
			index = current;
			if(++current >= capacity) {
				current = 0;
			}

			cacheMap.remove(cache[index].key);
		}

		cache[index].key = key;
		cache[index].value = value;
		cacheMap.put(key, cache[index]);
	}

	private void updateOrder(ArrayCacheEntity entity, Object value) {

		int index = entity.index;
		Object key = entity.key;

		if(!isFull()) {
			for(int i=index; i<size-1; i++) {
				 cache[i].key = cache[i+1].key;
				 cache[i].value = cache[i+1].value;
			}
			cache[size-1].key = key;
			cache[size-1].value = value;

			for(int i=index; i<size; i++) {
				cacheMap.put(cache[i].key, cache[i]);
			}
		}else {
			if(index == current) {
				cache[current].value = value;
				cacheMap.put(cache[current].key, cache[current]);
				if(++current >= capacity) {
					current = 0;
				}
			}else if(index < current) {
				if((current-index)*2 <= capacity) {
					for(int i=index; i<current-1; i++) {
						cache[i].key = cache[i+1].key;
						cache[i].value = cache[i+1].value;
					}
					cache[current-1].key = key;
					cache[current-1].value = value;

					for(int i=index; i<current; i++) {
						cacheMap.put(cache[i].key, cache[i]);
					}
				}else {
					for(int i=index; i > 0; i--) {
						cache[i].key = cache[i-1].key;
						cache[i].value = cache[i-1].value;
					}
					cache[0].key = cache[capacity-1].key;
					cache[0].value = cache[capacity-1].value;
					for(int i=capacity-1; i>current; i--) {
						cache[i].key = cache[i-1].key;
						cache[i].value = cache[i-1].value;
					}
					cache[current].key = key;
					cache[current].value = value;
					int i = index, count = capacity - (current - index - 1);
					while(count-- > 0) {
						i = (i+capacity) % capacity;
						cacheMap.put(cache[i].key, cache[i]);
						i--;
					}

					current++;
				}
			}else {
				if((index-current+1)*2 <= capacity) {
					for(int i=index; i>current; i--) {
						cache[i].key = cache[i-1].key;
						cache[i].value = cache[i-1].value;
					}
					cache[current].key = key;
					cache[current].value = value;
					for(int i=current; i<=index; i++) {
						cacheMap.put(cache[i].key, cache[i]);
					}

					current++;
				}else {
					for(int i=index; i<capacity-1; i++) {
						cache[i].key = cache[i+1].key;
						cache[i].value = cache[i+1].value;
					}
					cache[capacity-1].key = cache[0].key;
					cache[capacity-1].value = cache[0].value;
					for(int i=0; i<current-1; i++) {
						cache[i].key = cache[i+1].key;
						cache[i].value = cache[i+1].value;
					}
					cache[current-1].key = key;
					cache[current-1].value = value;
					int i = index, count = capacity - (index-current);
					while(count-- > 0) {
						i %= capacity;
						cacheMap.put(cache[i].key, cache[i]);
						i++;
					}
				}
			}
		}

	}

	@Override
	public synchronized void clear() {
		for(int i=0; i<cache.length; i++) {
			cache[i] = new ArrayCacheEntity(i);
		}
	}

	static class ArrayCacheEntity extends CacheEntity {
		int index;

		ArrayCacheEntity(int index) {
			this.index = index;
		}
	}

}

ListCacheFIFO實現類:

package com.lee.java.learning.cache;

public class ListCacheFIFO extends AbstractCache {

	private static final long serialVersionUID = -1804440082070314369L;

	protected ListCacheEntity head;

	public ListCacheFIFO() {
		this(AbstractCache.DEFAULT_CAPACITY);
	}

	public ListCacheFIFO(int capacity) {
		super(capacity);
		head = new ListCacheEntity();
		head.pre = head.next = head;
	}

	@Override
	public synchronized void addElement(Object key, Object value) {
		Object obj = null;
		obj = cacheMap.get(key);

		if(obj != null) {
			ListCacheEntity entity = (ListCacheEntity)obj;
			entity.value = value;

			// update order
			entity.pre.next = entity.next;
			entity.next.pre = entity.pre;
			entity.pre = entity.next = null;
			moveToFront(entity, head);

			cacheMap.put(key, entity);

			return;
		}

		if(!isFull()) {
			size++;
		}else {
			ListCacheEntity temp = head.next;
			temp.pre.next = temp.next;
			temp.next.pre = temp.pre;
			temp.pre = temp.next = null;
			cacheMap.remove(temp.key);
		}

		ListCacheEntity entity = new ListCacheEntity();
		entity.key = key;
		entity.value = value;
		moveToFront(entity, head);

		cacheMap.put(key, entity);
	}

	protected void moveToFront(ListCacheEntity movedEntity, ListCacheEntity desEntity) {
		desEntity.pre.next = movedEntity;
		movedEntity.pre = desEntity.pre;
		desEntity.pre = movedEntity;
		movedEntity.next = desEntity;
	}

	@Override
	public synchronized void clear() {
		super.clear();
		head.pre = head.next = head;
	}

	static class ListCacheEntity extends CacheEntity {
		ListCacheEntity pre;
		ListCacheEntity next;

		ListCacheEntity() {
			pre = next = null;
		}
	}

}

CacheLRU實現類:

package com.lee.java.learning.cache;

import com.lee.java.learning.cache.ListCacheFIFO.ListCacheEntity;

public class CacheLRU extends ListCacheFIFO {

	private static final long serialVersionUID = 1122993622171221931L;

	public CacheLRU() {
		this(ListCacheFIFO.DEFAULT_CAPACITY);
	}

	public CacheLRU(int capacity) {
		super(capacity);
		head = new CacheLRUEntity();
		head.pre = head.next = head;
	}

	@Override
	public synchronized void addElement(Object key, Object value) {
		Object obj = null;
		obj = cacheMap.get(key);

		if(obj != null) {
			CacheLRUEntity entity = (CacheLRUEntity)obj;
			entity.value = value;
			entity.count++;

			// update order
			CacheLRUEntity it = (CacheLRUEntity) entity.next;
			while(it != head && it.count <= entity.count) {
				it = (CacheLRUEntity)it.next;
			}
			if(it == entity.next) {
				// do nothing
			}else {
				entity.pre.next = entity.next;
				entity.next.pre = entity.pre;
				entity.pre = entity.next = null;
				moveToFront(entity, it);
			}

			cacheMap.put(key, entity);

			return;
		}

		if(!isFull()) {
			size++;
		}else {
			ListCacheEntity temp = head.next;
			temp.pre.next = temp.next;
			temp.next.pre = temp.pre;
			temp.pre = temp.next = null;
			cacheMap.remove(temp.key);
		}

		CacheLRUEntity entity = new CacheLRUEntity();
		entity.count = 1;
		entity.key = key;
		entity.value = value;

		CacheLRUEntity it = (CacheLRUEntity) head.next;
		while(it != head && it.count <= entity.count) {
			it = (CacheLRUEntity)it.next;
		}
		moveToFront(entity, it);

		cacheMap.put(key, entity);
	}

	@Override
	public synchronized Object getElement(Object key) {
		Object obj = null;
		obj = cacheMap.get(key);
		if(obj != null) {
			CacheLRUEntity entity = (CacheLRUEntity)obj;
			entity.count++;

			// update order
			CacheLRUEntity it = (CacheLRUEntity) entity.next;
			while(it != head && it.count <= entity.count) {
				it = (CacheLRUEntity)it.next;
			}
			if(it == entity.next) {
				// do nothing
			}else {
				entity.pre.next = entity.next;
				entity.next.pre = entity.pre;
				entity.pre = entity.next = null;
				moveToFront(entity, it);
			}

			return entity.value;
		}

		return null;
	}

	static class CacheLRUEntity extends ListCacheEntity {
		int count;

		CacheLRUEntity() {
			count = 0;
		}
	}

}