write throughキャッシュの改造

write throughキャッシュをHashMapとLinkedListで実現してみましたが、JRE1.4からはLinkedHashMapという、LRUアルゴリズムの実現のためにあるようなMapの実装がjava.utilに追加されています。LinkdedHashMapはHashMapと似ていますが、ハッシュのエントリの順番に意味があるという点でHashMapと異なっています(HashMapはエントリの順番が保証されない)。LinkedHashMapを単純に作成するとエントリはマップに追加した順番が有効になっています。エントリの更新を行っても順番に影響はありません。これはこれで便利なものですが、LRUで使うのはコンストラクタでaccessOrderというbooleanのパラメータをtrueにして作成するものです。こうするとエントリの順番がアクセスした順番、つまり最も最後にアクセスしたものが一番最後になる、という都合の良いものです。またremoveEldestEntry()メソッドをオーバーライドすることで、キャッシュサイズがいっぱいになったときに一番古いエントリを削除する、という部分が自動的に実現できますので、非常に便利です。LinkdedHashMapを使ったwrite through cacheの実現は単純にLinkedHashMapのメソッドを呼び出すだけです。ただしLinkedHashMap自身は同期化されませんので、synchronized指定をします。

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Write Through Cacheの実装。
 * LinkedHashMapを使用。
 * @author PoisonSoft
 */
public class WriteThroughCache2 implements Cache {

	/**
	 * エントリがキャッシュサイズを超える場合に最も古いものが削除されるマップ
	 */
	public class LruLinkedHashMap extends LinkedHashMap {
		/**
		 * コンストラクタ
		 * @param capacity キャッシュエントリ数
		 * @param loadFactor 負荷係数
		 */
		public LruLinkedHashMap(int capacity, float loadFactor) {
			super(capacity, loadFactor, true);
		}
		
		/**
		 * 一番古いエントリを消すかどうか
		 * @param entry 最も古いエントリ
		 * @return 一番古いエントリを消す場合はtrue
		 * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
		 */
		protected boolean removeEldestEntry(Entry envtry) {
			if (this.size() > cacheSize) {
				return true;
			} else {
				return false;
			}
		}
	}

	/** デフォルトの負荷係数 */
	private float DEFAULT_LOAD_FACTOR = 0.75f;

	/** キャッシュの最小エントリ数 */
	public static final int MIN_CACHE_SIZE = 4;

	/** キャッシュソース */
	private CacheSource source = null;
	
	/** キャッシュサイズ */
	private int cacheSize = MIN_CACHE_SIZE;
	
	/** キャッシュエントリのマップ */
	private Map cacheMap = null;
	
	/**
	 * コンストラクタ
	 * @param source キャッシュソース
	 */
	public WriteThroughCache2(CacheSource source, int size) {
		this.source = source;
		if (size < MIN_CACHE_SIZE) {
			size = MIN_CACHE_SIZE;
		}
		this.cacheSize = size;
		this.cacheMap = new LruLinkedHashMap(size, DEFAULT_LOAD_FACTOR);
	}

	/**
	 * 値の読み出し
	 * @param key キャッシュキー
	 * @return キャッシュ値
	 * @throws CacheException
	 * @see Cache#read(java.lang.Object)
	 */
	public synchronized Object read(Object key) throws CacheException {
		return cacheMap.get(key);
	}

	/**
	 * 値の書き込み
	 * @param key キャッシュキー
	 * @param value キャッシュ値
	 * @throws CacheException
	 * @see Cache#write(java.lang.Object, java.lang.Object)
	 */
	public void write(Object key, Object value) throws CacheException {
		cacheMap.put(key, value);
		try {
			source.write(key, value);
		} catch (CacheSourceException e) {
			throw new CacheException(e);
		}
	}

	/**
	 * キーの削除
	 * @param key キャッシュキー
	 * @throws CacheException
	 * @see Cache#delete(java.lang.Object)
	 */
	public synchronized void delete(Object key) throws CacheException {
		if (cacheMap.containsKey(key)) {
			cacheMap.remove(key);
		}
		try {
			source.delete(key);
		} catch (CacheSourceException e) {
			throw new CacheException(e);
		}
	}
}

MapとListを使って実装するよりずいぶん簡単です。同期化についてもメソッドにsynchronized指定をするのではなく、

Map cacheMap = Collections.synchronizedMap(new LruLinkedHashMap());
の様に同期化されたマップにすればすっきりします。


汎用Cacheシステムの開発目次  システム開発室  PoisonSoft