All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.persistit.encoding.ObjectCache Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/**
 * Copyright © 2005-2012 Akiban Technologies, Inc.  All rights reserved.
 * 
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * This program may also be available under different license terms.
 * For more information, see www.akiban.com or contact [email protected].
 * 
 * Contributors:
 * Akiban Technologies, Inc.
 */

package com.persistit.encoding;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

import com.persistit.Key;
import com.persistit.KeyState;

/**
 * 

* A simple cache for deserialized objects. This cache is loosely patterned * after WeakHashMap, except that an entry is eligible to be removed when the * value associated with a key is softly referenced, not the key. *

*

* Note: although many of the methods of this class take an argument of type * {@link com.persistit.Key}, the map is actually stored using immutable * {@link com.persistit.KeyState} objects. Therefore if you modify the state of * a Key after using it in a call to {@link #put}, the mapping will * remain intact. A new KeyState object is created only when a new * member is being added to the cache. Looking up, removing or replacing a value * by key does not require construction of a new KeyState because * Key and KeyState implement compatible * equals and hashCode methods. *

* */ public class ObjectCache { /** * Constant used to indicate the value associated with a particular * Key is known to be null rather than unknown. */ public final static Object NULL = new Object(); /** * Initial size of the backing hash table. */ public final static int DEFAULT_INITIAL_SIZE = 16; private Entry[] _entries = new Entry[DEFAULT_INITIAL_SIZE]; private int _size; private int _deadCount; private final int _deadCountThreshold = 25; private final ReferenceQueue> _queue = new ReferenceQueue>(); private static class Entry { Object _key; SoftReference _reference; Entry _next; } /** * Remove all entries from this cache. */ public synchronized void clear() { _entries = new Entry[DEFAULT_INITIAL_SIZE]; _size = 0; _deadCount = 0; } /** * Remove all dead entries from this cache. A dead reference is one for * which the referent value of the SoftReference has been * removed by the garbage collector. */ public synchronized void clean() { processQueue(0); resize(); } /** *

* Inserts a key/value pair in the cache and returns the formerly cached * value, if there is one. The object used to identify the value in the * cache is a {@link com.persistit.KeyState} constructed from the the * supplied {@link com.persistit.Key}. Key and * KeyState implement compatible equals and * hashCode methods so that they can be used interchangeably to * access values in maps. *

*

* The value is held within a SoftReference. This means that as * soon as no other object holds a strong reference to the value, the * garbage collector is permitted to remove it and to set the referent of * the SoftReference to null. Therefore an * application may put a value into this cache and then at some * later time receive null from the get method. *

*

* This method handles null values in a special way. The code *

* *
     * objectCache.put(key, null)
     * 
* *
stores the constant {@link #NULL} to represent the * knowledge that the value associated with key is null, rather * than unknown. The {@link #get} method returns null * regardless of whether the value in the cache is NULL or is not present. * Applications should use the {@link #isCached} method to determine whether * the value is affirmatively null. Alternatively, applications * may use the {@link #getWithNull} method and then test the result for * equality with NULL as a distinct value. *

* * @param key * The Key used to identify the value. When storing * a new key, this method constructs and stores an immutable * KeyState from the Key so that the * mapping remains valid even if the Key * subsequently changes. * * @param value * An Object that is to be associated with key. The value may be * any class, and it may be null. * * @return The former value. */ public synchronized Object put(final Key key, Object value) { if (value == null) value = NULL; Object previousValue = null; boolean found = false; final int offset = offset(key, _entries.length); Entry entry = _entries[offset]; while (entry != null && !found) { if (entry._key.equals(key)) { found = true; previousValue = entry._reference.get(); if (previousValue == NULL) previousValue = null; } else entry = entry._next; } if (!found) { entry = new Entry(); entry._key = new KeyState(key); entry._next = _entries[offset]; _entries[offset] = entry; _size++; } entry._reference = new SoftReference(value, _queue); processQueue(_deadCountThreshold); if (_size > _entries.length * 0.75) resize(); return previousValue; } /** *

* Return the Object value associated with the key if it is present in the * cache; otherwise null. This method does not differentiate * between a stored null value and a value missing from the * cache. Applications should use the {@link #isCached} method or * {@link #getWithNull} to determine whether the there is a value associated * with the key. *

* * @param key * The key to which the value is associated. * * @return The value, or null if there is none. */ public synchronized Object get(final Key key) { final int offset = offset(key, _entries.length); Entry entry = _entries[offset]; while (entry != null) { if (entry._key.equals(key)) { final Object value = entry._reference.get(); if (value == NULL) return null; else return value; } entry = entry._next; } return null; } /** *

* Return the Object value associated with the key if it is present in the * cache; otherwise null. This method differentiates between * between a stored null value and a value missing from the * cache. In the former case, this method returns {@link #NULL}. * Applications can test the result as follows:

* *
     * Object o = cache.getWithNull(key);
     * if (o == null) ... //not cached
     * else if (o == ObjectCache.NULL) .. // value is known to be null
     * else ... // o represents a cached object
     * 
* *
*

* * @param key * The key to which the value is associated. * * @return The value, {@link #NULL} if the value associated with the key is * known to be null, or null if the there is no cached * value for this key. */ public synchronized Object getWithNull(final Key key) { final int offset = offset(key, _entries.length); Entry entry = _entries[offset]; while (entry != null) { if (entry._key.equals(key)) { final Object value = entry._reference.get(); return value; } entry = entry._next; } return null; } /** * Indicates whether there is a value associated with the key. * * @param key * @return true if the cache contains a representation of the * value associated with the key; otherwise false */ public synchronized boolean isCached(final Key key) { final int offset = offset(key, _entries.length); Entry entry = _entries[offset]; while (entry != null) { if (entry._key.equals(key)) { return true; } entry = entry._next; } return false; } /** * Remove the entry for the specified key, if present, and return its former * value. * * @param key * The Key * * @return The value formerly associated in the cache with the supplied * key. The behavior is the same as * {@link #getWithNull}; that is, the returned value value is an * object, {@link #NULL} or null. */ public synchronized Object remove(final Key key) { Object value = null; final int offset = offset(key, _entries.length); Entry entry = _entries[offset]; if (entry == null) return null; if (entry._key.equals(key)) { _entries[offset] = entry._next; _size--; value = entry._reference.get(); } else { Entry next = entry._next; while (next != null) { if (next._key.equals(key)) { entry._next = next._next; _size--; value = next._reference.get(); break; } entry = next; next = entry._next; } } processQueue(_deadCountThreshold); if (_size < _entries.length * .25) resize(); return value; } /** * Recomputes the size needed to store the entries in the cache efficienty, * and if necessary, replaces the backing array store. */ private void resize() { int newSize = _size * 2; if (newSize < DEFAULT_INITIAL_SIZE) newSize = DEFAULT_INITIAL_SIZE; // If the length of the table is already in the ballpark, just // leave it. if (_entries.length > newSize && _entries.length < newSize + DEFAULT_INITIAL_SIZE) return; final Entry[] newEntries = new Entry[newSize]; for (int offset = 0; offset < _entries.length; offset++) { Entry entry = _entries[offset]; while (entry != null) { final int newOffset = offset(entry._key, newSize); final Entry next = entry._next; entry._next = newEntries[newOffset]; newEntries[newOffset] = entry; entry = next; } } _entries = newEntries; } /** *

* Counts the dead WeakReferences. If the total is above the supplied * threshold, scans the cache for and removes entries having dead * references. *

*

* Removing entries containing dead entries reduces memory consumption only * modestly, so it may be appropriate to raise the default dead count * threshold. *

* * @param deadCountThreshold */ private void processQueue(final int deadCountThreshold) { while (_queue.poll() != null) _deadCount++; if (_deadCount > deadCountThreshold) { for (int index = 0; index < _entries.length; index++) { Entry entry = _entries[index]; while (entry != null && entry._reference.get() == null) { entry = _entries[index] = entry._next; _size--; } if (entry != null) { Entry next; while ((next = entry._next) != null) { if (next._reference.get() == null) { entry._next = next._next; _size--; } else { entry = entry._next; } } } } _deadCount = 0; } } private int offset(final Object key, final int length) { return ((key.hashCode()) & 0x7FFFFFFF) % length; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy