Fixed length cache with a LRU replacement policy. : Cache « Development Class « Java






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

   
  








Related examples in the same category

1.A LRU (Least Recently Used) cache replacement policy
2.A Map that is size-limited using an LRU algorithm
3.A random cache replacement policy
4.A second chance FIFO (First In First Out) cache replacement policy
5.An LRU (Least Recently Used) cache replacement policy
6.Async LRU List
7.FIFO First In First Out cache replacement policy
8.Implementation of a Least Recently Used cache policy
9.Generic LRU Cache
10.LRU Cache
11.A Least Recently Used Cache
12.The class that implements a simple LRU cache
13.Map implementation for cache usage
14.Weak Cache Map
15.Provider for the application cache directories.
16.A small LRU object cache.
17.A least recently used (LRU) cache.
18.LRU Cache 2
19.A cache that purges values according to their frequency and recency of use and other qualitative values.
20.A thread-safe cache that keeps its values as java.lang.ref.SoftReference so that the cache is, in effect, managed by the JVM and kept as small as is required
21.Cache LRU
22.A FastCache is a map implemented with soft references, optimistic copy-on-write updates, and approximate count-based pruning.
23.A HardFastCache is a map implemented with hard references, optimistic copy-on-write updates, and approximate count-based pruning.