Skip to content

Instantly share code, notes, and snippets.

@zacscott
Last active December 16, 2015 18:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zacscott/5476061 to your computer and use it in GitHub Desktop.
Save zacscott/5476061 to your computer and use it in GitHub Desktop.
A small, compact in-memory cache designed for high data throughput.
package net.zeddev.util;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
/**
* <p>
* A small, compact in-memory cache designed for high data throughput.
* The cache can be simply integrated with existing source using the following
* pattern;
* </p>
*
* <p>
*<pre>
*FastCache<String, Object> cache = new FastCache<>(sizeOfCache);
*
* // the cache can then be used like so;
*
*if (cache.contains("element")) {
*
* // not in cache
* // do normal, expensive procesing here to get the data
* Object theData = ...;
*
* cache.set("element", theData);
*
*}
*
* // now get the data from the cache
*Object data = cache.get("element");
*
*</pre>
* </p>
*
* <p>
* <b>NOTE:</b> The cache does not work like a normal data collection, in that
* it is not guaranteed that elements inserted into the cache will remain there.
* This is due to the fact that weak references to store the data, so if there
* is a memory shortage the GC can reclaim the unnecessary memory consumed by
* the caches elements.
* </p>
*
* <p>
* <b>NOTE:</b> This file is released as a stand-alone utility. The original
* file can be found on GitHub Gist -
* <a href="https://gist.github.com/zscott92/5476061">here</a>.
* </p>
*
* @author Zachary Scott <zscott.dev@gmail.com>
*/
public class FastCache<K, V> {
// NOTE FastCache is not a subclass of Map (which would be much easier) as
// it should not be though of as a Map. The entries in the cache are
// not guarenteed to stay in the cache.
/** The maximum size any single <code>FastCache</code> can be (<code>65536</code>). */
public static final int MAX_SIZE = 65536;
// NOTE This is due to the way in which the hash code is mixed to create
// a unsigned value suitable as an array index (in unsignedHash()
// method below).
// the size of the cache (in elements)
private final int size;
// TODO Set size to nearest prime number, to improve collision ratio.
// the cache elements
private final SoftReference<CacheElement>[] elements;
// for SynchronizedFastCache nested class
private FastCache() {
size = 0;
elements = null;
}
/**
* Creates a new <code>FastCache</code> of the given size.
*
* @param capacity The maximum capacity of the cache (must be &gt; 0).
*/
public FastCache(int capacity) {
assert(capacity > 0);
size = capacity;
elements = (SoftReference<CacheElement>[]) Array.newInstance(SoftReference.class, size);
// initialise the cache elements (to null)
for (int i = 0; i < size; i++)
elements[i] = null;
}
/**
* Creates a new thread-safe <code>FastCache</code> of the given size.
*
* @param size The maximum capacity of the cache (must be &gt; 0).
* @return The new <code>FastCache</code> instance.
*/
public static final <K, V> FastCache<K, V> synchronizedInstance(int capacity) {
return new SynchronizedFastCache(new FastCache(capacity));
}
// converts a signed int hashCode to an unsigned value (unsigned 16 bit)
private int unsignedHash(Object obj) {
long hash = (long) obj.hashCode() + Integer.MAX_VALUE;
hash = hash >> 16 ^ hash;
return (int) hash & 0xffff;
}
/**
* Puts the given element in the cache.
*
* @param key The key of the cache element to be added to the cache (must not be <code>null</code>).
* @param value The element value to be added to the cache.
*/
public void put(K key, V value) {
assert(key != null);
CacheElement element = new CacheElement(key, value);
elements[unsignedHash(key) % size] = new SoftReference(element);
}
/**
* Removes the given element in the cache.
*
* @param key The key of the cache element to be removed (must not be <code>null</code>).
*/
public void remove(K key) {
assert(key != null);
elements[unsignedHash(key) % size] = null;
}
/**
* Gets the given element from the cache.
* Returns <code>null</code> if the element is not present in the cache.
*
* @param key The key of the cache element to get (must not be <code>null</code>).
* @return The given element in the cache (<code>null</code> if not present).
*/
public V get(K key) {
SoftReference<CacheElement> ref = elements[unsignedHash(key) % size];
if (ref == null)
return null;
CacheElement element = ref.get();
if (element != null && element.getKey() != null && key.equals(element.getKey())) {
return element.getValue();
} else {
return null;
}
}
/**
* Returns whether the cache contains the given element.
*
* @param key The key of the element to check (must not be <code>null</code>).
* @return Whether the given element is in the cache.
*/
public boolean contains(K key) {
return get(key) != null;
}
/**
* Returns the capacity of the cache.
* i.e. The number of element it can contain.
*
* @return The capacity of the cache.
*/
public int getCapacity() {
return size;
}
// a synchronized decorator for FastCache
private static class SynchronizedFastCache<K, V> extends FastCache<K, V> {
private final FastCache cache;
public SynchronizedFastCache(final FastCache fastCache) {
cache = fastCache;
}
@Override
public synchronized void put(K key, V value) {
cache.put(key, value);
}
@Override
public synchronized void remove(K key) {
cache.remove(key);
}
@Override
public synchronized V get(K key) {
return (V) cache.get(key);
}
@Override
public synchronized boolean contains(K key) {
return cache.contains(key);
}
@Override
public synchronized int getCapacity() {
return cache.getCapacity();
}
}
// encapsulates a single element in the cache
private class CacheElement {
private K key;
private V value;
public CacheElement(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
/* Copyright (C) 2013 Zachary Scott <zscott.dev@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package net.zeddev.util;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* <p>
* The unit test for {@link FastCache}.
* </p>
*
* <p>
* <b>NOTE:</b> The {@code FastCache} class is released as a stand-alone utility
* and the original file can be found on GitHub Gist -
* <a href="https://gist.github.com/zscott92/5476061">here</a>.
* </p>
*
* @author Zachary Scott <zscott.dev@gmail.com>
*/
public class FastCacheTest {
public FastCacheTest() {
}
@Test
public void testPut() {
FastCache<String, String> instance = new FastCache<>(10);
instance.put("key1", "value1");
instance.put("key2", "value2");
String value1 = instance.get("key1");
assertEquals(value1, "value1");
String value2 = instance.get("key2");
assertEquals(value2, "value2");
}
@Test
public void testPutReplace() {
FastCache<String, String> instance = new FastCache<>(1);
instance.put("key1", "value1");
String value1 = instance.get("key1");
assertEquals(value1, "value1");
String value2 = instance.get("key2"); // key is not in cache
assertEquals(value2, null); // so should be null
}
@Test
public void testRemove() {
FastCache<String, String> instance = new FastCache<>(10);
instance.put("key", "value");
instance.remove("key");
String value = instance.get("key");
assertEquals(value, null); // must be removed from cache
}
@Test
public void testGet() {
FastCache<String, String> instance = new FastCache<>(10);
instance.put("key1", "value1");
Object value1 = instance.get("key1");
assertEquals(value1, "value1");
instance.put("key2", "value2");
Object value2 = instance.get("key2");
assertEquals(value2, "value2");
}
@Test
public void testContains() {
FastCache<String, String> instance = new FastCache<>(10);
instance.put("key", "value");
assertTrue(instance.contains("key"));
}
@Test
public void testGetCapacity() {
FastCache<String, String> instance;
instance = new FastCache<>(10);
assertEquals(instance.getCapacity(), 10);
instance = new FastCache<>(100);
assertEquals(instance.getCapacity(), 100);
instance = new FastCache<>(23);
assertEquals(instance.getCapacity(), 23);
}
@Test
public void testSynchronizedInstance() {
FastCache<String, String> instance = FastCache.synchronizedInstance(10);
// encapsultates a regular FastCache expierience
instance.put("key", "value");
assertTrue(instance.contains("key"));
String value = instance.get("key");
assertEquals(value, "value");
assertEquals(instance.getCapacity(), 10);
instance.remove("key");
value = instance.get("key");
assertEquals(value, null);
}
}
/* Copyright (C) 2013 Zachary Scott <zscott.dev@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment