Mega Code Archive

 
Categories / Java / Development Class
 

Fixed length cache with a LRU replacement policy

/*  * 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(); }