提交 dcc2163b authored 作者: Thomas Mueller's avatar Thomas Mueller

LIRS replacement algorithm

上级 a619d5c6
......@@ -6,6 +6,7 @@
package org.h2.test.store;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
import org.h2.dev.store.btree.CacheLirs;
import org.h2.test.TestBase;
......@@ -27,6 +28,7 @@ public class TestCache extends TestBase {
public void test() throws Exception {
testEdgeCases();
testClear();
testGetPutPeekRemove();
testLimitHot();
testLimitNonResident();
......@@ -36,20 +38,44 @@ public class TestCache extends TestBase {
}
private void testEdgeCases() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(0);
test.put(1, 10);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(1, 1);
test.put(1, 10, 100);
assertEquals(10, test.get(1).intValue());
try {
test.put(null, 10, 100);
fail();
} catch (NullPointerException e) {
// expected
}
try {
test.put(1, null, 100);
fail();
} catch (NullPointerException e) {
// expected
}
try {
test.setMaxMemory(0);
fail();
} catch (IllegalArgumentException e) {
// expected
}
try {
test.setAverageMemory(0);
fail();
} catch (IllegalArgumentException e) {
// expected
}
}
private void testGetPutPeekRemove() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4, 1);
test.put(1, 10);
test.put(2, 20);
test.put(3, 30);
assertNull(test.peek(4));
assertNull(test.get(4));
test.put(4, 40);
assertEquals("mem: 4 stack: 4 3 2 1 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident:");
// move middle to front
assertEquals(30, test.get(3).intValue());
assertEquals(20, test.get(2).intValue());
......@@ -58,77 +84,167 @@ public class TestCache extends TestBase {
assertEquals(20, test.get(2).intValue());
assertEquals(10, test.peek(1).intValue());
assertEquals(10, test.get(1).intValue());
assertEquals("mem: 4 stack: 1 2 3 4 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 1 2 3 4 cold: non-resident:");
test.put(3, 30);
assertEquals("mem: 4 stack: 3 1 2 4 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 3 1 2 4 cold: non-resident:");
// 5 is cold; will make 4 non-resident
test.put(5, 50);
assertEquals("mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4", toString(test));
verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4");
assertNull(test.peek(4));
assertNull(test.get(4));
assertEquals(10, test.get(1).intValue());
assertEquals(20, test.get(2).intValue());
assertEquals(30, test.get(3).intValue());
assertEquals("mem: 4 stack: 3 2 1 cold: 5 non-resident: 4", toString(test));
verify(test, "mem: 4 stack: 3 2 1 cold: 5 non-resident: 4");
assertEquals(50, test.get(5).intValue());
assertEquals("mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4", toString(test));
verify(test, "mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4");
assertEquals(50, test.get(5).intValue());
assertEquals("mem: 4 stack: 5 3 2 cold: 1 non-resident: 4", toString(test));
verify(test, "mem: 4 stack: 5 3 2 cold: 1 non-resident: 4");
// remove
assertTrue(test.remove(5));
assertFalse(test.remove(5));
assertEquals("mem: 3 stack: 3 2 1 cold: non-resident: 4", toString(test));
assertTrue(test.remove(4));
assertFalse(test.remove(4));
assertEquals("mem: 3 stack: 3 2 1 cold: non-resident:", toString(test));
assertEquals(50, test.remove(5).intValue());
assertNull(test.remove(5));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident: 4");
assertNull(test.remove(4));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
assertNull(test.remove(4));
verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:");
test.put(4, 40);
test.put(5, 50);
assertEquals("mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1", toString(test));
verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1");
test.get(5);
test.get(2);
test.get(3);
test.get(4);
assertEquals("mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1", toString(test));
assertTrue(test.remove(5));
assertEquals("mem: 3 stack: 4 3 2 cold: non-resident: 1", toString(test));
assertTrue(test.remove(2));
assertTrue(test.remove(1));
assertEquals("mem: 2 stack: 4 3 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1");
assertEquals(50, test.remove(5).intValue());
verify(test, "mem: 3 stack: 4 3 2 cold: non-resident: 1");
assertEquals(20, test.remove(2).intValue());
assertFalse(test.containsKey(1));
assertNull(test.remove(1));
assertFalse(test.containsKey(1));
verify(test, "mem: 2 stack: 4 3 cold: non-resident:");
test.put(1, 10);
test.put(2, 20);
assertEquals("mem: 4 stack: 2 1 4 3 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 2 1 4 3 cold: non-resident:");
test.get(1);
test.get(3);
test.get(4);
assertEquals("mem: 4 stack: 4 3 1 2 cold: non-resident:", toString(test));
assertTrue(test.remove(1));
assertEquals("mem: 3 stack: 4 3 2 cold: non-resident:", toString(test));
verify(test, "mem: 4 stack: 4 3 1 2 cold: non-resident:");
assertEquals(10, test.remove(1).intValue());
verify(test, "mem: 3 stack: 4 3 2 cold: non-resident:");
test.remove(2);
test.remove(3);
test.remove(4);
// test clear
test.clear();
assertEquals("mem: 0 stack: cold: non-resident:", toString(test));
verify(test, "mem: 0 stack: cold: non-resident:");
// strange situation where there is only a non-resident entry
test.put(1, 10);
test.put(2, 20);
test.put(3, 30);
test.put(4, 40);
test.put(5, 50);
assertTrue(test.containsValue(50));
verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1");
test.put(1, 10);
verify(test, "mem: 4 stack: 1 5 4 3 2 cold: 1 non-resident: 5");
assertFalse(test.containsValue(50));
test.remove(2);
test.remove(3);
test.remove(4);
verify(test, "mem: 1 stack: 1 cold: non-resident: 5");
assertTrue(test.containsKey(1));
test.remove(1);
assertFalse(test.containsKey(1));
verify(test, "mem: 0 stack: cold: non-resident: 5");
assertFalse(test.containsKey(5));
assertTrue(test.isEmpty());
// verify that converting a hot to cold entry will prune the stack
test.clear();
test.put(1, 10);
test.put(2, 20);
test.put(3, 30);
test.put(4, 40);
test.put(5, 50);
test.get(4);
test.get(3);
verify(test, "mem: 4 stack: 3 4 5 2 cold: 5 non-resident: 1");
test.put(6, 60);
verify(test, "mem: 4 stack: 6 3 4 5 2 cold: 6 non-resident: 5 1");
// this will prune the stack (remove entry 5 as entry 2 becomes cold)
test.get(6);
verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1");
}
private void testClear() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(40, 10);
for (int i = 0; i < 5; i++) {
test.put(i, 10 * i, 9);
}
verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0");
for (Entry<Integer, Integer> e : test.entrySet()) {
assertTrue(e.getKey() >= 1 && e.getKey() <= 4);
assertTrue(e.getValue() >= 10 && e.getValue() <= 40);
}
for (int x : test.values()) {
assertTrue(x >= 10 && x <= 40);
}
for (int x : test.keySet()) {
assertTrue(x >= 1 && x <= 4);
}
assertEquals(40, test.getMaxMemory());
assertEquals(10, test.getAverageMemory());
assertEquals(36, test.getUsedMemory());
assertEquals(4, test.size());
assertEquals(3, test.sizeHot());
assertEquals(1, test.sizeNonResident());
assertFalse(test.isEmpty());
// changing the limit is not supposed to modify the map
test.setMaxMemory(10);
assertEquals(10, test.getMaxMemory());
test.setMaxMemory(40);
test.setAverageMemory(1);
assertEquals(1, test.getAverageMemory());
test.setAverageMemory(10);
verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0");
// putAll uses the average memory
test.putAll(test);
verify(test, "mem: 40 stack: 4 3 2 1 cold: non-resident: 0");
test.clear();
verify(test, "mem: 0 stack: cold: non-resident:");
assertEquals(40, test.getMaxMemory());
assertEquals(10, test.getAverageMemory());
assertEquals(0, test.getUsedMemory());
assertEquals(0, test.size());
assertEquals(0, test.sizeHot());
assertEquals(0, test.sizeNonResident());
assertTrue(test.isEmpty());
}
private void testLimitHot() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(100);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(100, 1);
for (int i = 0; i < 300; i++) {
test.put(i, 10 * i);
}
assertEquals(199, test.getSize());
assertEquals(93, test.getHotSize());
assertEquals(99, test.getNonResidentSize());
assertEquals(100, test.size());
assertEquals(99, test.sizeNonResident());
assertEquals(93, test.sizeHot());
}
private void testLimitNonResident() {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(4, 1);
for (int i = 0; i < 20; i++) {
test.put(i, 10 * i);
}
assertEquals("mem: 4 stack: 19 18 17 16 3 2 1 cold: 19 non-resident: 18 17 16", toString(test));
verify(test, "mem: 4 stack: 19 18 17 16 3 2 1 cold: 19 non-resident: 18 17 16");
}
private void testBadHashMethod() {
......@@ -159,14 +275,14 @@ public class TestCache extends TestBase {
}
CacheLirs<BadHash, Integer> test = CacheLirs.newInstance(size * 2);
CacheLirs<BadHash, Integer> test = CacheLirs.newInstance(size * 2, 1);
for (int i = 0; i < size; i++) {
test.put(new BadHash(i), i);
}
for (int i = 0; i < size; i++) {
if (i % 3 == 0) {
assertTrue(test.remove(new BadHash(i)));
assertFalse(test.remove(new BadHash(i)));
assertEquals(i, test.remove(new BadHash(i)).intValue());
assertNull(test.remove(new BadHash(i)));
}
}
for (int i = 0; i < size; i++) {
......@@ -181,8 +297,8 @@ public class TestCache extends TestBase {
}
for (int i = 0; i < size; i++) {
if (i % 3 == 0) {
assertTrue(test.remove(new BadHash(i)));
assertFalse(test.remove(new BadHash(i)));
assertEquals(i, test.remove(new BadHash(i)).intValue());
assertNull(test.remove(new BadHash(i)));
}
}
for (int i = 0; i < size; i++) {
......@@ -198,7 +314,7 @@ public class TestCache extends TestBase {
boolean log = false;
int size = 20;
// cache size 11 (10 hot, 1 cold)
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(size / 2 + 1);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(size / 2 + 1, 1);
// init the cache with some dummy entries
for (int i = 0; i < size; i++) {
test.put(-i, -i * 10);
......@@ -248,7 +364,7 @@ public class TestCache extends TestBase {
int size = 10;
Random r = new Random(1);
for (int j = 0; j < 100; j++) {
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(size / 2);
CacheLirs<Integer, Integer> test = CacheLirs.newInstance(size / 2, 1);
HashMap<Integer, Integer> good = New.hashMap();
for (int i = 0; i < 10000; i++) {
int key = r.nextInt(size);
......@@ -306,4 +422,14 @@ public class TestCache extends TestBase {
return buff.toString();
}
private <K, V> void verify(CacheLirs<K, V> cache, String expected) {
String got = toString(cache);
assertEquals(expected, got);
int mem = 0;
for (K k : cache.keySet()) {
mem += cache.getMemory(k);
}
assertEquals(mem, cache.getUsedMemory());
}
}
......@@ -7,33 +7,82 @@
package org.h2.dev.store.btree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A cache.
* A LIRS cache.
* <p>
* This implementation is not multi-threading save.
*
* It is important to use a good hash function for the key (there is no guard against bad hash functions).
* This implementation is not multi-threading save. Null keys or null values are
* not allowed. There is no guard against bad hash functions, so it is important
* to the hash function of the key is good.
* <p>
* Each each entry is assigned a distinct memory size, and the cache will try to
* use at most the specified amount of memory. The memory unit is not relevant,
* however it is suggested to use bytes as the unit.
* <p>
* An implementation of the LIRS replacement algorithm from Xiaodong Zhang and
* Song Jiang as described in
* http://www.cse.ohio-state.edu/~zhang/lirs-sigmetrics-02.html with a few
* smaller changes: An additional queue for non-resident entries is used, to
* prevent unbound memory usage. The maximum size of this queue is at most the
* size of the rest of the stack. This implementation allows each entry to have
* a distinct memory size. At most 6.25% of the mapped entries are cold.
* size of the rest of the stack. About 5% of the mapped entries are cold.
*
* @author Thomas Mueller
*
* @param <K> the key type
* @param <V> the value type
*/
public class CacheLirs<K, V> {
public class CacheLirs<K, V> implements Map<K, V> {
/**
* The maximum memory this cache should use.
*/
private long maxMemory;
private long currentMemory;
/**
* The average memory used by one entry.
*/
private int averageMemory;
private int mapSize, stackSize, queueSize, queue2Size;
/**
* The currently used memory.
*/
private long usedMemory;
/**
* The number of entries in the map. This includes all hot and cold entries.
*/
private int mapSize;
/**
* The LIRS stack size. This includes all hot and some of the cold entries.
*/
private int stackSize;
/**
* The size of the LIRS queue for resident cold entries.
*/
private int queueSize;
/**
* The size of the LIRS queue for non-resident cold entries.
*/
private int queue2Size;
/**
* The map entries. The size is always a power of 2.
*/
private Entry<K, V>[] entries;
/**
* The bit mask that is applied to the key hash code to get the map index.
* The value is the size of the entries array minus one.
*/
private int mask;
/**
......@@ -59,43 +108,56 @@ public class CacheLirs<K, V> {
* @param averageMemory the average memory usage of an object
*/
private CacheLirs(long maxMemory, int averageMemory) {
this.maxMemory = maxMemory;
this.averageMemory = averageMemory;
setMaxMemory(maxMemory);
setAverageMemory(averageMemory);
clear();
}
/**
* Create a new cache.
* Create a new cache with the given size in number of entries.
*
* @param size the maximum number of elements
* @param maxMemory the maximum memory to use (1 or larger)
* @param averageMemory the average memory (1 or larger)
* @return the cache
*/
public static <K, V> CacheLirs<K, V> newInstance(int size) {
return new CacheLirs<K, V>(size, 1);
public static <K, V> CacheLirs<K, V> newInstance(int maxMemory, int averageMemory) {
return new CacheLirs<K, V>(maxMemory, averageMemory);
}
/**
* Clear the cache.
*/
public void clear() {
// calculate the size of the map array
// assume a fill factor of at most 75%
long maxLen = (long) (maxMemory / averageMemory / 0.75);
// the size needs to be a power of 2
long l = 8;
while (l < maxLen) {
l += l;
}
// the array size is at most 2^31 elements
int len = (int) Math.min(1L << 31, l);
// the bit mask has all bits set
mask = len - 1;
// initialize the stack and queue heads
stack = new Entry<K, V>();
stack.stackPrev = stack.stackNext = stack;
queue = new Entry<K, V>();
queue.queuePrev = queue.queueNext = queue;
queue2 = new Entry<K, V>();
queue2.queuePrev = queue2.queueNext = queue2;
// first set to null - avoiding out of memory
entries = null;
@SuppressWarnings("unchecked")
Entry<K, V>[] e = new Entry[len];
entries = e;
currentMemory = 0;
mapSize = 0;
usedMemory = 0;
stackSize = queueSize = queue2Size = 0;
}
......@@ -111,22 +173,38 @@ public class CacheLirs<K, V> {
return e == null ? null : e.value;
}
/**
* Get the memory used for the given key.
*
* @param key the key
* @return the memory, or 0 if there is no resident entry
*/
public int getMemory(K key) {
Entry<K, V> e = find(key);
return e == null ? null : e.memory;
}
/**
* Get an entry if the entry is cached. This method adjusts the internal
* state of the cache, to ensure commonly used entries stay in the cache.
*
* @param key the key
* @param key the key (may not be null)
* @return the value, or null if not found
*/
public V get(K key) {
public V get(Object key) {
Entry<K, V> e = find(key);
if (e == null || e.value == null) {
// either the entry was not found, or it was a non-resident entry
return null;
} else if (e.isHot()) {
if (e != stack.stackNext) {
// move a hot entries to the top of the stack
// unless it is already there
boolean wasEnd = e == stack.stackPrev;
removeFromStack(e);
if (wasEnd) {
// if moving the last entry, the last entry
// could not be cold, which is not allowed
pruneStack();
}
addToStack(e);
......@@ -134,11 +212,17 @@ public class CacheLirs<K, V> {
} else {
removeFromQueue(e);
if (e.stackNext != null) {
// resident cold entries become hot
// if they are on the stack
removeFromStack(e);
// which means a hot entry needs to become cold
convertOldestHotToCold();
} else {
// cold entries that are not on the stack
// move to the front of the queue
addToQueue(queue, e);
}
// in any case, the cold entry is moved to the top of the stack
addToStack(e);
}
return e.value;
......@@ -148,11 +232,11 @@ public class CacheLirs<K, V> {
* Add an entry to the cache. This method is the same as adding an entry
* with the average memory size.
*
* @param key the key
* @param value the value
* @param key the key (may not be null)
* @param value the value (may not be null)
*/
public void put(K key, V value) {
put(key, value, averageMemory);
public V put(K key, V value) {
return put(key, value, averageMemory);
}
/**
......@@ -160,63 +244,77 @@ public class CacheLirs<K, V> {
* yet. This method will usually mark unknown entries as cold and known
* entries as hot.
*
* @param key the key
* @param value the value
* @param key the key (may not be null)
* @param value the value (may not be null)
* @param memory the memory used for the given entry
*/
public void put(K key, V value, int memory) {
if (find(key) != null) {
public V put(K key, V value, int memory) {
if (value == null) {
throw new NullPointerException();
}
V old;
Entry<K, V> e = find(key);
if (e == null) {
old = null;
} else {
old = e.value;
remove(key);
}
Entry<K, V> e = new Entry<K, V>();
e = new Entry<K, V>();
e.key = key;
e.value = value;
e.memory = memory;
int index = key.hashCode() & mask;
e.chained = entries[index];
e.mapNext = entries[index];
entries[index] = e;
currentMemory += memory;
if (currentMemory > maxMemory && mapSize > 0) {
usedMemory += memory;
if (usedMemory > maxMemory && mapSize > 0) {
// an old entry needs to be removed
evict(e);
}
mapSize++;
// added entries are always added to the stack
addToStack(e);
return old;
}
/**
* Remove an entry.
*
* @param key the key
* @param key the key (may not be null)
* @return true if the entry was found (resident or non-resident)
*/
public boolean remove(K key) {
public V remove(Object key) {
int hash = key.hashCode();
int index = hash & mask;
Entry<K, V> e = entries[index];
if (e == null) {
return false;
return null;
}
V old;
if (e.key.equals(key)) {
entries[index] = e.chained;
old = e.value;
entries[index] = e.mapNext;
} else {
Entry<K, V> last;
do {
last = e;
e = e.chained;
e = e.mapNext;
if (e == null) {
return false;
return null;
}
} while (!e.key.equals(key));
last.chained = e.chained;
old = e.value;
last.mapNext = e.mapNext;
}
mapSize--;
currentMemory -= e.memory;
usedMemory -= e.memory;
if (e.stackNext != null) {
removeFromStack(e);
}
if (e.isHot()) {
// when removing a hot entry, convert the newest cold entry to hot,
// so that we keep the number of hot entries
// when removing a hot entry, the newest cold entry gets hot,
// so the number of hot entries does not change
e = queue.queueNext;
if (e != queue) {
removeFromQueue(e);
......@@ -228,21 +326,33 @@ public class CacheLirs<K, V> {
removeFromQueue(e);
}
pruneStack();
return true;
return old;
}
/**
* Evict cold entries (resident and non-resident) until the memory limit is
* reached.
*
* @param newCold a new cold entry
*/
private void evict(Entry<K, V> newCold) {
// ensure there are not too many hot entries:
// left shift of 5 is multiplication by 32, that means if there are less
// than 1/32 (3.125%) cold entries, a new hot entry needs to become cold
while ((queueSize << 5) < mapSize) {
convertOldestHotToCold();
}
// the new cold entry is at the top of the queue
addToQueue(queue, newCold);
while (currentMemory > maxMemory) {
// the oldest resident cold entries become non-resident
while (usedMemory > maxMemory) {
Entry<K, V> e = queue.queuePrev;
currentMemory -= e.memory;
usedMemory -= e.memory;
removeFromQueue(e);
e.value = null;
e.memory = 0;
addToQueue(queue2, e);
// the size of the non-resident-cold entries needs to be limited
while (queue2Size + queue2Size > stackSize) {
e = queue2.queuePrev;
remove(e.key);
......@@ -251,27 +361,41 @@ public class CacheLirs<K, V> {
}
private void convertOldestHotToCold() {
// the last entry of the stack is known to be hot
Entry<K, V> last = stack.stackPrev;
// remove from stack - which is done anyway in the stack pruning, but we
// can do it here as well
removeFromStack(last);
// adding an entry to the queue will make it cold
addToQueue(queue, last);
pruneStack();
}
/**
* Ensure the last entry of the stack is cold.
*/
private void pruneStack() {
while (true) {
Entry<K, V> last = stack.stackPrev;
if (last == stack || last.isHot()) {
break;
}
// the cold entry is still in the queue
removeFromStack(last);
}
}
private Entry<K, V> find(K key) {
/**
* Try to find an entry in the map.
*
* @param key the key
* @return the entry (might be a non-resident)
*/
private Entry<K, V> find(Object key) {
int hash = key.hashCode();
Entry<K, V> e = entries[hash & mask];
while (e != null && !e.key.equals(key)) {
e = e.chained;
e = e.mapNext;
}
return e;
}
......@@ -323,53 +447,114 @@ public class CacheLirs<K, V> {
}
/**
* Get the number of mapped entries (resident and non-resident).
* Get the list of keys. This method allows to view the internal state of
* the cache.
*
* @return the number of entries
* @param cold if true, the keys for the cold entries are returned
* @param nonResident true for non-resident entries
* @return the key list
*/
public int getSize() {
return mapSize;
public List<K> keys(boolean cold, boolean nonResident) {
ArrayList<K> s = new ArrayList<K>();
if (cold) {
Entry<K, V> start = nonResident ? queue2 : queue;
for (Entry<K, V> e = start.queueNext; e != start; e = e.queueNext) {
s.add(e.key);
}
} else {
for (Entry<K, V> e = stack.stackNext; e != stack; e = e.stackNext) {
s.add(e.key);
}
}
return s;
}
/**
* Get the number of hot entries.
* Get the number of resident entries.
*
* @return the number of entries
*/
public int getHotSize() {
return mapSize - queueSize - queue2Size;
public int size() {
return mapSize - queue2Size;
}
/**
* Get the number of non-resident entries.
* Check whether there are any resident entries in the map.
*
* @return the number of entries
* @return true if there are no keys
*/
public int getNonResidentSize() {
return queue2Size;
public boolean isEmpty() {
return size() == 0;
}
/**
* Get the list of keys for this map. This method allows to view the internal
* state of the cache.
* Check whether there is a resident entry for the given key.
*
* @param cold if true only the keys for the cold entries are returned
* @param nonResident true for non-resident entries
* @return the key set
* @return true if the key is in the map
*/
public List<K> keys(boolean cold, boolean nonResident) {
ArrayList<K> s = new ArrayList<K>();
if (cold) {
Entry<K, V> start = nonResident ? queue2 : queue;
for (Entry<K, V> e = start.queueNext; e != start; e = e.queueNext) {
s.add(e.key);
public boolean containsKey(Object key) {
Entry<K, V> e = find(key);
return e != null && e.value != null;
}
} else {
/**
* Check whether there are any keys for the given value.
*
* @return true if there is a key for this value
*/
public boolean containsValue(Object value) {
return values().contains(value);
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
public Set<K> keySet() {
HashSet<K> set = new HashSet<K>();
for (Entry<K, V> e = stack.stackNext; e != stack; e = e.stackNext) {
s.add(e.key);
set.add(e.key);
}
for (Entry<K, V> e = queue.queueNext; e != queue; e = e.queueNext) {
set.add(e.key);
}
return s;
return set;
}
public Collection<V> values() {
ArrayList<V> list = new ArrayList<V>();
for (K k : keySet()) {
list.add(get(k));
}
return list;
}
public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) {
map.put(k, find(k).value);
}
return map.entrySet();
}
/**
* Get the number of hot entries in the cache.
*
* @return the number of entries
*/
public int sizeHot() {
return mapSize - queueSize - queue2Size;
}
/**
* Get the number of non-resident entries in the cache.
*
* @return the number of entries
*/
public int sizeNonResident() {
return queue2Size;
}
/**
......@@ -378,12 +563,56 @@ public class CacheLirs<K, V> {
* @return the used memory
*/
public long getUsedMemory() {
return currentMemory;
return usedMemory;
}
/**
* Set the maximum memory this cache should use. This will not immediately
* cause entries to get removed however; it will only change the limit.
*
* @param maxMemory the maximum size (1 or larger)
*/
public void setMaxMemory(long maxMemory) {
if (maxMemory <= 0) {
throw new IllegalArgumentException("Max memory must be larger than 0");
}
this.maxMemory = maxMemory;
}
/**
* Get the maximum memory to use.
*
* @return the maximum memory
*/
public long getMaxMemory() {
return maxMemory;
}
/**
* Set the average memory used per entry. It is used to calculate the size
* of the map.
*
* @param averageMemory the average memory used (1 or larger)
*/
public void setAverageMemory(int averageMemory) {
if (averageMemory <= 0) {
throw new IllegalArgumentException("Average memory must be larger than 0");
}
this.averageMemory = averageMemory;
}
/**
* Get the average memory used per entry.
*
* @return the average memory
*/
public int getAverageMemory() {
return averageMemory;
}
/**
* A cache entry. Each entry is either hot (low inter-reference recency;
* lir), cold (high inter-reference recency; hir), or non-resident-cold. Hot
* LIR), cold (high inter-reference recency; HIR), or non-resident-cold. Hot
* entries are in the stack only. Cold entries are in the queue, and may be
* in the stack. Non-resident-cold entries have their value set to null and
* are in the stack and in the non-resident queue.
......@@ -392,13 +621,53 @@ public class CacheLirs<K, V> {
* @param <V> the value type
*/
static class Entry<K, V> {
/**
* The key.
*/
K key;
/**
* The value. Set to null for non-resident-cold entries.
*/
V value;
/**
* The estimated memory used.
*/
int memory;
Entry<K, V> stackPrev, stackNext;
Entry<K, V> queuePrev, queueNext;
Entry<K, V> chained;
/**
* The next entry in the stack.
*/
Entry<K, V> stackNext;
/**
* The previous entry in the stack.
*/
Entry<K, V> stackPrev;
/**
* The next entry in the queue (either the resident queue or the
* non-resident queue).
*/
Entry<K, V> queueNext;
/**
* The previous entry in the queue.
*/
Entry<K, V> queuePrev;
/**
* The next entry in the map
*/
Entry<K, V> mapNext;
/**
* Whether this entry is hot. Cold entries are in one of the two queues.
*
* @return whether the entry is hot
*/
boolean isHot() {
return queueNext == null;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论