Java tutorial
/* * Copyright (c) 1998-2004 Caucho Technology -- all rights reserved This file is * part of Resin(R) Open Source Each copy or derived work must preserve the * copyright notice and this notice unmodified. Resin Open Source is free * software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. Resin Open * Source is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE, or any warranty of NON-INFRINGEMENT. See the GNU * General Public License for more details. You should have received a copy of * the GNU General Public License along with Resin Open Source; if not, write to * the Free SoftwareFoundation, Inc. 59 Temple Place, Suite 330 Boston, MA * 02111-1307 USA @author Scott Ferguson */ // originally: package com.caucho.util; //package org.aitools.programd.util; import java.util.ArrayList; import java.util.Iterator; /** * <p> * Fixed length cache with a LRU replacement policy. If cache items implement * CacheListener, they will be informed when they're removed from the cache. * </p> * <p> * Null keys are not allowed. LRUCache is synchronized. * </p> * * @param <K> the key * @param <V> the value */ public class LRUCache<K, V> { private static final Integer NULL = new Integer(0); /* * hash table containing the entries. Its size is twice the capacity so it * will always remain at least half empty */ protected CacheItem<K, V>[] _entries; /* maximum allowed entries */ private int _capacity; /* size 1 capacity is half the actual capacity */ private int _capacity1; /* mask for hash mapping */ private int _mask; /* number of items in the cache seen once */ private int _size1; /* head of the LRU list */ private CacheItem<K, V> _head1; /* tail of the LRU list */ private CacheItem<K, V> _tail1; /* number of items in the cache seen more than once */ private int _size2; /* head of the LRU list */ private CacheItem<K, V> _head2; /* tail of the LRU list */ private CacheItem<K, V> _tail2; /* hit count statistics */ private volatile long _hitCount; /* miss count statistics */ private volatile long _missCount; /** * Create the LRU cache with a specific capacity. Originally called * "LruCache". Some minor changes (in coding style and formatting) made by * Noel. * * @param initialCapacity minimum capacity of the cache */ @SuppressWarnings("unchecked") public LRUCache(int initialCapacity) { int capacity; for (capacity = 16; capacity < 2 * initialCapacity; capacity *= 2) { // Do nothing except this loop. } this._entries = new CacheItem[capacity]; this._mask = capacity - 1; this._capacity = initialCapacity; this._capacity1 = this._capacity / 2; } /** * @return the current number of entries in the cache */ public int size() { return this._size1 + this._size2; } /** * Clears the cache */ public void clear() { ArrayList<CacheListener> listeners = null; synchronized (this) { for (int i = this._entries.length - 1; i >= 0; i--) { CacheItem<K, V> item = this._entries[i]; if (item != null) { if (item._value instanceof CacheListener) { if (listeners == null) { listeners = new ArrayList<CacheListener>(); } listeners.add((CacheListener) item._value); } } this._entries[i] = null; } this._size1 = 0; this._head1 = null; this._tail1 = null; this._size2 = 0; this._head2 = null; this._tail2 = null; } for (int i = listeners == null ? -1 : listeners.size() - 1; i >= 0; i--) { CacheListener listener = listeners.get(i); listener.removeEvent(); } } /** * Get an item from the cache and make it most recently used. * * @param key key to lookup the item * @return the matching object in the cache */ public V get(K key) { Object okey = key; if (okey == null) okey = NULL; int hash = okey.hashCode() & this._mask; int count = this._size1 + this._size2 + 1; synchronized (this) { for (; count >= 0; count--) { CacheItem<K, V> item = this._entries[hash]; if (item == null) { this._missCount++; return null; } if (item._key == key || item._key.equals(key)) { updateLru(item); this._hitCount++; return item._value; } hash = (hash + 1) & this._mask; } this._missCount++; } return null; } /** * Puts a new item in the cache. If the cache is full, remove the LRU item. * * @param key key to store data * @param value value to be stored * @return old value stored under the key */ public V put(K key, V value) { V oldValue = put(key, value, true); if (oldValue instanceof CacheListener) ((CacheListener) oldValue).removeEvent(); return oldValue; } /** * Puts a new item in the cache. If the cache is full, remove the LRU item. * * @param key key to store data * @param value value to be stored * @return the value actually stored */ public V putIfNew(K key, V value) { V oldValue = put(key, value, false); if (oldValue != null) { return oldValue; } // otherwise... return value; } /** * Puts a new item in the cache. If the cache is full, remove the LRU item. * * @param key key to store data * @param value value to be stored * @param replace whether or not to replace the old value * @return old value stored under the key */ private V put(K key, V value, boolean replace) { Object okey = key; if (okey == null) { okey = NULL; } // remove LRU items until we're below capacity while (this._capacity <= this._size1 + this._size2) { removeTail(); } int hash = key.hashCode() & this._mask; int count = this._size1 + this._size2 + 1; V oldValue = null; synchronized (this) { for (; count > 0; count--) { CacheItem<K, V> item = this._entries[hash]; // No matching item, so create one if (item == null) { item = new CacheItem<K, V>(key, value); this._entries[hash] = item; this._size1++; item._next = this._head1; if (this._head1 != null) { this._head1._prev = item; } else { this._tail1 = item; } this._head1 = item; return null; } // matching item gets replaced if (item._key == okey || item._key.equals(okey)) { updateLru(item); oldValue = item._value; if (replace) { item._value = value; } break; } hash = (hash + 1) & this._mask; } } if (replace && oldValue instanceof CacheListener) { ((CacheListener) oldValue).removeEvent(); } return null; } /** * Put item at the head of the used-twice lru list. This is always called * while synchronized. * * @param item the item to put at the head of the list */ private void updateLru(CacheItem<K, V> item) { CacheItem<K, V> prev = item._prev; CacheItem<K, V> next = item._next; if (item._isOnce) { item._isOnce = false; if (prev != null) { prev._next = next; } else { this._head1 = next; } if (next != null) { next._prev = prev; } else { this._tail1 = prev; } item._prev = null; if (this._head2 != null) { this._head2._prev = item; } else { this._tail2 = item; } item._next = this._head2; this._head2 = item; this._size1--; this._size2++; } else { if (prev == null) { return; } prev._next = next; item._prev = null; item._next = this._head2; this._head2._prev = item; this._head2 = item; if (next != null) { next._prev = prev; } else { this._tail2 = prev; } } } /** * @return the last item in the LRU */ public boolean removeTail() { CacheItem<K, V> tail; if (this._capacity1 <= this._size1) { tail = this._tail1; } else { tail = this._tail2; } if (tail == null) { return false; } remove(tail._key); return true; } /** * Removes an item from the cache * * @param key the key to remove * @return the value removed */ public V remove(K key) { Object okey = key; if (okey == null) { okey = NULL; } int hash = key.hashCode() & this._mask; int count = this._size1 + this._size2 + 1; V value = null; synchronized (this) { for (; count > 0; count--) { CacheItem<K, V> item = this._entries[hash]; if (item == null) { return null; } if (item._key == okey || item._key.equals(okey)) { this._entries[hash] = null; CacheItem<K, V> prev = item._prev; CacheItem<K, V> next = item._next; if (item._isOnce) { this._size1--; if (prev != null) { prev._next = next; } else { this._head1 = next; } if (next != null) { next._prev = prev; } else { this._tail1 = prev; } } else { this._size2--; if (prev != null) { prev._next = next; } else { this._head2 = next; } if (next != null) { next._prev = prev; } else { this._tail2 = prev; } } value = item._value; // Shift colliding entries down for (int i = 1; i <= count; i++) { int nextHash = (hash + i) & this._mask; CacheItem<K, V> nextItem = this._entries[nextHash]; if (nextItem == null) { break; } this._entries[nextHash] = null; refillEntry(nextItem); } break; } hash = (hash + 1) & this._mask; } } if (count < 0) { throw new RuntimeException("internal cache error"); } return value; } /** * Put the item in the best location available in the hash table. * * @param item the item to put in the best location available */ private void refillEntry(CacheItem<K, V> item) { int baseHash = item._key.hashCode(); for (int count = 0; count < this._size1 + this._size2 + 1; count++) { int hash = (baseHash + count) & this._mask; if (this._entries[hash] == null) { this._entries[hash] = item; return; } } } /** * @return the keys stored in the cache */ public Iterator<K> keys() { KeyIterator<K, V> iter = new KeyIterator<K, V>(this); iter.init(this); return iter; } /** * @param oldIter the old iterator to use * @return keys stored in the cache using an old iterator */ @SuppressWarnings("unchecked") public Iterator<K> keys(Iterator<K> oldIter) { KeyIterator<K, V> iter; try { iter = (KeyIterator<K, V>) oldIter; } catch (ClassCastException e) { throw new Exception("Passed a non-ValueIterator to values().", e); } iter.init(this); return oldIter; } /** * @return the values in the cache */ public Iterator<V> values() { ValueIterator<K, V> iter = new ValueIterator<K, V>(this); iter.init(this); return iter; } /** * @param oldIter the old iterator * @return the values of the old iterator */ @SuppressWarnings("unchecked") public Iterator<V> values(Iterator<V> oldIter) { ValueIterator<K, V> iter; try { iter = (ValueIterator<K, V>) oldIter; } catch (ClassCastException e) { throw new Exception("Passed a non-ValueIterator to values().", e); } iter.init(this); return oldIter; } /** * @return the entries */ public Iterator<Entry<K, V>> iterator() { return new EntryIterator(this); } /** * @return the hit count. */ public long getHitCount() { return this._hitCount; } /** * @return the miss count. */ public long getMissCount() { return this._missCount; } /** * A cache item * * @param <K_> the key * @param <V_> the value */ static class CacheItem<K_, V_> { LRUCache.CacheItem<K_, V_> _prev; LRUCache.CacheItem<K_, V_> _next; K_ _key; V_ _value; int _index; boolean _isOnce; CacheItem(K_ key, V_ value) { this._key = key; this._value = value; this._isOnce = true; } } /** * Iterator of cache keys * * @param <K_> the key * @param <V_> the value */ static class KeyIterator<K_, V_> implements Iterator<K_> { private LRUCache<K_, V_> _cache; private int _i = -1; KeyIterator(LRUCache<K_, V_> cache) { this._cache = cache; } void init(LRUCache<K_, V_> cache) { this._cache = cache; this._i = -1; } /** * @return the next entry in the cache */ public boolean hasNext() { CacheItem<K_, V_>[] entries = this._cache._entries; int length = entries.length; for (this._i++; this._i < length; this._i++) { if (entries[this._i] != null) { this._i--; return true; } } return false; } /** * @return the next value */ public K_ next() { CacheItem<K_, V_>[] entries = this._cache._entries; int length = entries.length; for (this._i++; this._i < length; this._i++) { CacheItem<K_, V_> entry = entries[this._i]; if (entry != null) { return entry._key; } } return null; } /** * @see java.util.Iterator#remove() */ public void remove() { throw new UnsupportedOperationException(); } } /** * Iterator of cache values * * @param <K_> the key * @param <V_> the value */ static class ValueIterator<K_, V_> implements Iterator<V_> { private LRUCache<K_, V_> _cache; private int _i = -1; ValueIterator(LRUCache<K_, V_> cache) { init(cache); } void init(LRUCache<K_, V_> cache) { this._cache = cache; this._i = -1; } /** * @return the next entry in the cache. */ public boolean hasNext() { CacheItem<K_, V_>[] entries = this._cache._entries; int length = entries.length; int i = this._i + 1; for (; i < length; i++) { if (entries[i] != null) { this._i = i - 1; return true; } } this._i = i; return false; } /** * @return the next value */ public V_ next() { CacheItem<K_, V_>[] entries = this._cache._entries; int length = entries.length; int i = this._i + 1; for (; i < length; i++) { CacheItem<K_, V_> entry = entries[i]; if (entry != null) { this._i = i; return entry._value; } } this._i = i; return null; } /** * @see java.util.Iterator#remove() */ public void remove() { throw new UnsupportedOperationException(); } } /** * Interface for entry iterator; * * @param <K_> the key * @param <V_> the value */ public interface Entry<K_, V_> { /** * @return the key */ public K_ getKey(); /** * @return the value */ public V_ getValue(); } /** * Iterator of cache values */ class EntryIterator implements Iterator<Entry<K, V>>, Entry<K, V> { private int _i = -1; private LRUCache<K, V> _cache; /** * Creates a new EntryIterator using the given cache. * * @param cache the cache to use */ public EntryIterator(LRUCache<K, V> cache) { this._cache = cache; } /** * @see java.util.Iterator#hasNext() */ public boolean hasNext() { int i = this._i + 1; CacheItem<K, V>[] entries = this._cache._entries; int length = entries.length; for (; i < length && entries[i] == null; i++) { // Do nothing but this loop. } this._i = i - 1; return i < length; } /** * @return the next entry * @see java.util.Iterator#next() */ public Entry<K, V> next() { int i = this._i + 1; CacheItem<K, V>[] entries = this._cache._entries; int length = entries.length; for (; i < length && entries[i] == null; i++) { // Do nothing but this loop. } this._i = i; if (this._i < length) { return this; } // otherwise... return null; } /** * @return the key */ public K getKey() { CacheItem<K, V>[] entries = this._cache._entries; if (this._i < this._cache._entries.length) { CacheItem<K, V> entry = entries[this._i]; return entry != null ? entry._key : null; } return null; } /** * @return the value */ public V getValue() { CacheItem<K, V>[] entries = this._cache._entries; if (this._i < this._cache._entries.length) { CacheItem<K, V> entry = entries[this._i]; return entry != null ? entry._value : null; } return null; } /** * @see java.util.Iterator#remove() */ public void remove() { CacheItem<K, V>[] entries = this._cache._entries; if (this._i < this._cache._entries.length) { CacheItem<K, V> entry = entries[this._i]; if (entry != null) { LRUCache.this.remove(entry._key); } } } } } interface CacheListener { /** * Notifies the cache entry that it's been removed from the cache. */ public void removeEvent(); }