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

com.aliasi.util.HardFastCache Maven / Gradle / Ivy

Go to download

This is the original Lingpipe: http://alias-i.com/lingpipe/web/download.html There were not made any changes to the source code.

There is a newer version: 4.1.2-JL1.0
Show newest version
package com.aliasi.util;

import java.util.AbstractMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;


/**
 * A HardFastCache is a map implemented with hard
 * references, optimistic copy-on-write updates, and approximate
 * count-based pruning.  It is intended for scalable multi-threaded
 * caches.  It sacrifices recall of mappings for speed of put and get
 * operations along with conservative memory guarantees.
 *
 * 

Note: The class {@link FastCache} is nearly identical, * but with hash buckets wrapped in soft references for automatic * resizing by the garbage collector. * *

The basis of the cache is a fixed-size hash map, based on values * returned by objects' hashCode() and * equals(Object) methods. * *

The map entries in the hash map are stored in buckets held by * ordinary references. Thus entries in the mapping may be garbage * collected. In the implementation, whole hash buckets are * collected, which is safe and highly efficient but may require some * additional recomputation of values that were removed by pruning. * *

Entries are stored in the map using a very optimistic update. * No synchronization at all is performed on the cache entries or * their counts. A copy-on-write strategy coupled with Java's memory * model for references ensures that the cache remains consistent, if * not complete. What this means is that multiple threads may both * try to cache a mapping and only one will be saved and/or * incremented in count. * *

When the table approximately exceeds the specified load factor, * the thread performing the add will perform a garbage collection by * reducing reference counts by half, rounding down, and removing * entries with zero counts. Pruning is subject to the caveats * mentioned in the last paragraph. Counts are not guaranteed to be * accurate. Pruning itself is synchronized and more conservatively * copy-on-write. By setting the load factor to * Double.POSITIVE_INFINITY there will be never be any * pruning done by this class. * *

A fast cache acts as a mapping with copy-on-write semantics. * Equality and iteration are defined as usual, with the caveat that * the snapshot taken of the elements may not be up to date. Iterators * may be used concurrently, but their remove methods do not affect * the underlying cache. * * For information on copy-on-write and optimistic updates, * see section 2.4 of: * *

    *
  • Doug Lea. 2000. * Concurrent Programming in Java. Second Edition. * Addison-Wesley. *
* * @author Bob Carpenter * @version 3.8.3 * @since LingPipe3.5.1 * @param the type of keys in the map * @param the type of values in the map */ public class HardFastCache extends AbstractMap { private static final double DEFAULT_LOAD_FACTOR = 0.5; private final Record[] mBuckets; private volatile int mNumEntries = 0; private int mMaxEntries; /** * Constrcut a fast cache of the specified size and default load * factor. The default load factor is 0.5. * * @param size Size of cache. * @throws IllegalArgumentException if the size is less than 2. */ public HardFastCache(int size) { this(size,DEFAULT_LOAD_FACTOR); } /** * Construct a fast cache of the specified size and load factor. * The size times the load factor must be greater than or equal to * 1. When the (approximate) number of entries exceeds the * load factor times the size, the cache is pruned. * * @param size Size of the cache. * @param loadFactor Load factor of the cache. * @throws IllegalArgumentException If the size times the load * factor is less than one. */ public HardFastCache(int size, double loadFactor) { mMaxEntries = (int) (loadFactor * (double) size); if (mMaxEntries < 1) { String msg = "size * loadFactor must be > 0." + " Found size=" + size + " loadFactor=" + loadFactor; throw new IllegalArgumentException(msg); } // required for array alloc @SuppressWarnings({"unchecked","rawtypes"}) Record[] bucketsTemp = (Record[]) new Record[size]; mBuckets = bucketsTemp; } /** * Returns the value of the specified key or null if * there is no value attached. Note that the argument is not * the generic <K> key type, but Object * to match the requirements of java.util.Map. * *

Warning: Because of the approximate cache-like * behavior of this class, key-value pairs previously added * by the {@link #put(Object,Object)} method may disappear. * * @param key Mapping key. * @return The value for the specified key. */ @Override public V get(Object key) { int bucketId = bucketId(key); for (Record record = mBuckets[bucketId]; record != null; record = record.mNextRecord) { if (record.mKey.equals(key)) { ++record.mCount; return record.mValue; } } return null; } int bucketId(Object key) { return java.lang.Math.abs(key.hashCode() % mBuckets.length); } /** * Sets the value of the specified key to the specified value. * If there is already a value for the specified key, the count * is incremented, but the value is not replaced. * *

Warning: Because of the approximate cache-like * behavior of this class, setting the value of a key with this * method is not guaranteed to replace older values or remain in * the mapping until the next call to {@link #get(Object)}. * * @param key Mapping key. * @param value New value for the specified key. * @return null, even if there is an existing * value for the specified key. */ @Override public V put(K key, V value) { int bucketId = bucketId(key); Record firstRecord = mBuckets[bucketId]; for (Record record = firstRecord; record != null; record = record.mNextRecord) { if (record.mKey.equals(key)) { ++record.mCount; // increment instead return null; // already there, spec allows val return } } prune(); Record record = new Record(key,value,firstRecord); mBuckets[bucketId] = record; ++mNumEntries; return null; } /** * Prunes this cache by (approximately) dividing the counts of * entries by two and removing the ones with zero counts. If the * cache is not full (more entries than size times load factor), * no pruning will be done. * *

Warning: This operation is approximate in the sense * that the optimistic update strategy applied is not guaranteed * to actually do any pruning or decrements of counts. */ public void prune() { synchronized (this) { if (mNumEntries < mMaxEntries) return; int count = 0; for (int i = 0; i < mBuckets.length; ++i) { Record record = mBuckets[i]; Record prunedRecord = prune(record); mBuckets[i] = prunedRecord; for (Record r = prunedRecord; r != null; r = r.mNextRecord) ++count; } mNumEntries = count; } } final Record prune(Record inRecord) { Record record = inRecord; while (record != null && (record.mCount = (record.mCount >>> 1)) == 0) record = record.mNextRecord; if (record == null) return null; record.mNextRecord = prune(record.mNextRecord); return record; } /** * Returns a snapshot of the entries in this map. * This set is not backed by this cache, so that changes * to the cache do not affect the cache and vice-versa. * * @return The set of entries in this cache. */ @Override public Set> entrySet() { HashSet> entrySet = new HashSet>(); for (int i = 0; i < mBuckets.length; ++i) for (Record record = mBuckets[i]; record != null; record = record.mNextRecord) entrySet.add(record); return entrySet; } static final class Record implements Map.Entry { final K mKey; final V mValue; Record mNextRecord; int mCount; Record(K key, V value) { this(key,value,null); } Record(K key, V value, Record nextRecord) { this(key,value,nextRecord,1); } Record(K key, V value, Record nextRecord, int count) { mKey = key; mValue = value; mNextRecord = nextRecord; mCount = count; } public K getKey() { return mKey; } public V getValue() { return mValue; } public V setValue(V value) { String msg = "Cache records may not be set."; throw new UnsupportedOperationException(msg); } // equals & hashcode specified by Map.Entry interface @Override public int hashCode() { return (mKey==null ? 0 : mKey.hashCode()) ^ (mValue==null ? 0 : mValue.hashCode()); } @Override @SuppressWarnings("rawtypes") public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e2 = (Map.Entry) o; return (mKey==null ? e2.getKey()==null : mKey.equals(e2.getKey())) && (mValue==null ? e2.getValue()==null : mValue.equals(e2.getValue())); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy