/**
* ConcurrentARC
* Copyright 2009 by Michael Peter Christen, mc@yacy.net, Frankfurt a. M., Germany
* First released 17.04.2009 at http://yacy.net
*
* $LastChangedDate$
* $LastChangedRevision$
* $LastChangedBy$
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package net.yacy.cora.storage;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* This is a simple cache using two generations of hashtables to store the content with a LFU strategy.
* The Algorithm is described in a slightly more complex version as Adaptive Replacement Cache, "ARC".
* For details see http://www.almaden.ibm.com/cs/people/dmodha/ARC.pdf
* or http://en.wikipedia.org/wiki/Adaptive_Replacement_Cache
* This version omits the ghost entry handling which is described in ARC, and keeps both cache levels
* at the same size.
*/
public final class ConcurrentARC<K, V> extends AbstractMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, ARC<K, V> {
private final int mask;
private final ARC<K, V> arc[];
/**
* create a concurrent ARC based on a HashARC. The type of the key elements must implement a hashing function
* @param cacheSize the number of maximum entries
* @param partitions the number of partitions
*/
@SuppressWarnings("unchecked")
public ConcurrentARC(final int cacheSize, final int partitions) {
int m = 1;
while (m < partitions) m = m * 2;
int partitionSize = cacheSize / m;
if (partitionSize < 4) partitionSize = 4;
this.arc = (ARC<K, V>[]) Array.newInstance(HashARC.class, m);
for (int i = 0; i < this.arc.length; i++) this.arc[i] = new HashARC<K, V>(partitionSize);
m -= 1;
this.mask = m;
}
/**
* create a concurrent ARC based on a ComparableARC
* @param cacheSize the number of maximum entries
* @param partitions the number of partitions
* @param comparator a comparator for the key object which may be of type byte[]
*/
@SuppressWarnings("unchecked")
public ConcurrentARC(final int cacheSize, final int partitions, final Comparator<? super K> comparator) {
int m = 1;
while (m < partitions) m = m * 2;
int partitionSize = cacheSize / m;
if (partitionSize < 4) partitionSize = 4;
this.arc = (ARC<K, V>[]) Array.newInstance(ComparableARC.class, m);
for (int i = 0; i < this.arc.length; i++) this.arc[i] = new ComparableARC<K, V>(partitionSize, comparator);
m -= 1;
this.mask = m;
}
/**
* put a value to the cache.
* @param s
* @param v
*/
@Override
public final void insert(final K s, final V v) {
this.arc[getPartition(s)].insert(s, v);
}
/**
* put a value to the cache if there was not an entry before
* do not return a previous content value
* @param s
* @param v
*/
@Override
public void insertIfAbsent(final K s, final V v) {
this.arc[getPartition(s)].insertIfAbsent(s, v);
}
/**
* put a value to the cache if there was not an entry before
* return a previous content value
* @param s
* @param v
* @return the value before inserting the new value
*/
@Override
public V putIfAbsent(final K s, final V v) {
return this.arc[getPartition(s)].putIfAbsent(s, v);
}
/**
* put a value to the cache.
* @param s
* @param v
*/
@Override
public final V put(final K s, final V v) {
return this.arc[getPartition(s)].put(s, v);
}
/**
* get a value from the cache.
* @param s
* @return the value
*/
@SuppressWarnings("unchecked")
@Override
public final V get(final Object s) {
return this.arc[getPartition(s)].get((K) s);
}
/**
* check if the map contains the value
* @param value
* @return the keys that have the given value
*/
@Override
public Collection<K> getKeys(final V value) {
final ArrayList<K> keys = new ArrayList<K>();
for (final ARC<K, V> element : this.arc)
keys.addAll(element.getKeys(value));
return keys;
}
/**
* check if the map contains the key
* @param s
* @return
*/
@SuppressWarnings("unchecked")
@Override
public final boolean containsKey(final Object s) {
return this.arc[getPartition(s)].containsKey((K) s);
}
/**
* remove an entry from the cache
* @param s
* @return the old value
*/
@SuppressWarnings("unchecked")
@Override
public final V remove(final Object s) {
return this.arc[getPartition(s)].remove((K) s);
}
/**
* clear the cache
*/
@Override
public final void clear() {
for (final ARC<K, V> a: this.arc) a.clear();
}
/**
* get the size of the ARC.
* @return the complete number of entries in the ARC cache
*/
@Override
public final int size() {
int s = 0;
for (final ARC<K, V> a: this.arc) s += a.size();
return s;
}
/**
* iterator implements the Iterable interface
*/
@Override
public Iterator<java.util.Map.Entry<K, V>> iterator() {
return entrySet().iterator();
}
/**
* Return a Set view of the mappings contained in this map.
* This method is the basis for all methods that are implemented
* by a AbstractMap implementation
*
* @return a set view of the mappings contained in this map
*/
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
final Set<Map.Entry<K, V>> m = new HashSet<Map.Entry<K, V>>();
for (final ARC<K, V> a: this.arc) {
for (final Map.Entry<K, V> entry: a.entrySet()) m.add(entry);
}
return m;
}
/**
* a hash code for this ARC
* @return a hash code
*/
@Override
public int hashCode() {
return this.arc.hashCode();
}
//private static String latestObject = "";
/**
* return in which partition the Object belongs
* This function uses the objects hashCode() function
* except for byte[] keys
* @return the partition number
*/
private int getPartition(final Object x) {
if (x instanceof byte[]) {
int h = 0;
for (final byte c: (byte[])x) h = 31 * h + (c & 0xFF);
final int p = h & this.mask;
//String o = UTF8.String((byte[]) x); try { if (o.equals(latestObject)) throw new RuntimeException("ConcurrentARC: p = " + p + ", objectb = " + o); } catch (final Exception e) { Log.logException(e); } latestObject = o;
return p;
}
final int p = x.hashCode() & this.mask;
//String o = x.toString(); try { if (o.equals(latestObject)) throw new RuntimeException("ConcurrentARC: p = " + p + ", objecto = " + o); } catch (final Exception e) { Log.logException(e); } latestObject = o;
return p;
}
public static void main(final String[] args) {
final Random r = new Random();
final int testsize = 10000;
final ARC<String, String> a = new ConcurrentARC<String, String>(testsize * 3, Runtime.getRuntime().availableProcessors());
final Map<String, String> b = new HashMap<String, String>();
String key, value;
for (int i = 0; i < testsize; i++) {
key = "k" + r.nextInt();
value = "v" + r.nextInt();
a.insertIfAbsent(key, value);
b.put(key, value);
}
// now put half of the entries AGAIN into the ARC
int h = testsize / 2;
for (final Map.Entry<String, String> entry: b.entrySet()) {
a.put(entry.getKey(), entry.getValue());
if (h-- <= 0) break;
}
// test correctness
for (final Map.Entry<String, String> entry: b.entrySet()) {
if (!a.containsKey(entry.getKey())) {
System.out.println("missing: " + entry.getKey());
continue;
}
if (!a.get(entry.getKey()).equals(entry.getValue())) {
System.out.println("wrong: a = " + entry.getKey() + "," + a.get(entry.getKey()) + "; v = " + entry.getValue());
}
}
System.out.println("finished test!");
}
}