-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LRU and LFU caches implemented. #592
- Loading branch information
Nikita
committed
Aug 31, 2016
1 parent
5e72484
commit 8d5fba1
Showing
8 changed files
with
1,008 additions
and
0 deletions.
There are no files selected for viewing
524 changes: 524 additions & 0 deletions
524
redisson/src/main/java/org/redisson/misc/AbstractCacheMap.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* Copyright 2016 Nikita Koksharov | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.redisson.misc; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* | ||
* @author Nikita Koksharov | ||
* | ||
* @param <K> | ||
* @param <V> | ||
*/ | ||
public interface Cache<K, V> extends Map<K, V> { | ||
|
||
V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit); | ||
|
||
} |
134 changes: 134 additions & 0 deletions
134
redisson/src/main/java/org/redisson/misc/LFUCacheMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/** | ||
* Copyright 2016 Nikita Koksharov | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.redisson.misc; | ||
|
||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentNavigableMap; | ||
import java.util.concurrent.ConcurrentSkipListMap; | ||
|
||
/** | ||
* LFU (least frequently used) cache. | ||
* | ||
* @author Nikita Koksharov | ||
* | ||
* @param <K> | ||
* @param <V> | ||
*/ | ||
public class LFUCacheMap<K, V> extends AbstractCacheMap<K, V> { | ||
|
||
public static class MapKey implements Comparable<MapKey> { | ||
|
||
private Long accessCount; | ||
private CachedValue cachedValue; | ||
|
||
public MapKey(Long accessCount, CachedValue cachedValue) { | ||
super(); | ||
this.accessCount = accessCount; | ||
this.cachedValue = cachedValue; | ||
} | ||
|
||
@Override | ||
public int compareTo(MapKey o) { | ||
int compare = accessCount.compareTo(o.accessCount); | ||
if (compare == 0) { | ||
if (!cachedValue.equals(o.cachedValue)) { | ||
return ((Integer)cachedValue.hashCode()).compareTo(o.cachedValue.hashCode()); | ||
} | ||
return 0; | ||
} | ||
return compare; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "MapKey [accessCount=" + accessCount + "]"; | ||
} | ||
|
||
} | ||
|
||
private final ConcurrentNavigableMap<MapKey, CachedValue> accessMap = new ConcurrentSkipListMap<MapKey, CachedValue>(); | ||
|
||
public LFUCacheMap(int size, long timeToLiveInMillis, long maxIdleInMillis) { | ||
super(size, timeToLiveInMillis, maxIdleInMillis); | ||
} | ||
|
||
@Override | ||
protected void onValueCreate(CachedValue value) { | ||
MapKey key = toKey(value); | ||
accessMap.put(key, value); | ||
} | ||
|
||
@Override | ||
protected void onValueRead(CachedValue value) { | ||
addAccessCount(value, 1); | ||
} | ||
|
||
private MapKey toKey(CachedValue value) { | ||
return new MapKey(value.getAccessCount(), value); | ||
} | ||
|
||
@Override | ||
protected void onValueRemove(CachedValue value) { | ||
MapKey key = toKey(value); | ||
if (accessMap.remove(key) == null) { | ||
throw new IllegalStateException(); | ||
} | ||
} | ||
|
||
private void addAccessCount(CachedValue value, long count) { | ||
synchronized (value) { | ||
if (count < 0 && value.getAccessCount() == 0) { | ||
return; | ||
} | ||
|
||
MapKey key = toKey(value); | ||
if (accessMap.remove(key) == null) { | ||
throw new IllegalStateException(); | ||
} | ||
|
||
if (count < 0) { | ||
count = -Math.min(value.getAccessCount(), -count); | ||
} | ||
value.addAccessCount(count); | ||
|
||
key = toKey(value); | ||
accessMap.put(key, value); | ||
} | ||
} | ||
|
||
@Override | ||
protected void onMapFull() { | ||
Map.Entry<MapKey, CachedValue> entry = accessMap.pollFirstEntry(); | ||
map.remove(entry.getValue().getKey(), entry.getValue()); | ||
|
||
if (entry.getValue().getAccessCount() == 0) { | ||
return; | ||
} | ||
|
||
// TODO optimize | ||
// decrease all values | ||
for (CachedValue value : accessMap.values()) { | ||
addAccessCount(value, -entry.getValue().getAccessCount()); | ||
} | ||
} | ||
|
||
@Override | ||
public void clear() { | ||
accessMap.clear(); | ||
super.clear(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* Copyright 2016 Nikita Koksharov | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.redisson.misc; | ||
|
||
import java.util.Queue; | ||
import java.util.concurrent.ConcurrentLinkedQueue; | ||
|
||
/** | ||
* LRU (least recently used) cache. | ||
* | ||
* @author Nikita Koksharov | ||
* | ||
* @param <K> | ||
* @param <V> | ||
*/ | ||
public class LRUCacheMap<K, V> extends AbstractCacheMap<K, V> { | ||
|
||
private final Queue<CachedValue> queue = new ConcurrentLinkedQueue<CachedValue>(); | ||
|
||
public LRUCacheMap(int size, long timeToLiveInMillis, long maxIdleInMillis) { | ||
super(size, timeToLiveInMillis, maxIdleInMillis); | ||
} | ||
|
||
@Override | ||
protected void onValueCreate(CachedValue value) { | ||
queue.add(value); | ||
} | ||
|
||
@Override | ||
protected void onValueRemove(CachedValue value) { | ||
queue.remove(value); | ||
} | ||
|
||
@Override | ||
protected void onValueRead(CachedValue value) { | ||
// move value to tail of queue | ||
synchronized (value) { | ||
queue.remove(value); | ||
queue.add(value); | ||
} | ||
} | ||
|
||
@Override | ||
protected void onMapFull() { | ||
CachedValue value = queue.poll(); | ||
map.remove(value.getKey(), value); | ||
} | ||
|
||
@Override | ||
public void clear() { | ||
queue.clear(); | ||
super.clear(); | ||
} | ||
|
||
} |
48 changes: 48 additions & 0 deletions
48
redisson/src/main/java/org/redisson/misc/NoneCacheMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* Copyright 2016 Nikita Koksharov | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.redisson.misc; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* | ||
* @author Nikita Koksharov | ||
* | ||
* @param <K> | ||
* @param <V> | ||
*/ | ||
public class NoneCacheMap<K, V> extends AbstractCacheMap<K, V> { | ||
|
||
public NoneCacheMap(long timeToLiveInMillis, long maxIdleInMillis) { | ||
super(0, timeToLiveInMillis, maxIdleInMillis); | ||
} | ||
|
||
@Override | ||
public V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { | ||
CachedValue entry = new CachedValue(key, value, ttlUnit.toMillis(ttl), maxIdleUnit.toMillis(maxIdleTime)); | ||
onValueCreate(entry); | ||
CachedValue prevCachedValue = map.put(key, entry); | ||
if (prevCachedValue != null && !prevCachedValue.isExpired()) { | ||
return (V) prevCachedValue.getValue(); | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
protected void onMapFull() { | ||
} | ||
|
||
} |
78 changes: 78 additions & 0 deletions
78
redisson/src/test/java/org/redisson/misc/LFUCacheMapTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package org.redisson.misc; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.junit.Test; | ||
|
||
public class LFUCacheMapTest { | ||
|
||
@Test | ||
public void testMaxIdleTimeEviction() throws InterruptedException { | ||
Cache<Integer, Integer> map = new LFUCacheMap<Integer, Integer>(2, 0, 0); | ||
map.put(1, 0, 0, TimeUnit.MILLISECONDS, 400, TimeUnit.MILLISECONDS); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
Thread.sleep(200); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
Thread.sleep(200); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
Thread.sleep(200); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
Thread.sleep(410); | ||
assertThat(map.keySet()).isEmpty(); | ||
} | ||
|
||
@Test | ||
public void testTTLEviction() throws InterruptedException { | ||
Cache<Integer, Integer> map = new LFUCacheMap<Integer, Integer>(2, 0, 0); | ||
map.put(1, 0, 500, TimeUnit.MILLISECONDS, 0, TimeUnit.MILLISECONDS); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
Thread.sleep(100); | ||
assertThat(map.get(1)).isEqualTo(0); | ||
assertThat(map.keySet()).containsOnly(1); | ||
Thread.sleep(500); | ||
assertThat(map.keySet()).isEmpty(); | ||
} | ||
|
||
@Test | ||
public void testSizeLFUEviction() throws InterruptedException { | ||
Cache<Integer, Integer> map = new LFUCacheMap<Integer, Integer>(3, 0, 0); | ||
|
||
map.put(1, 0); | ||
map.put(2, 0); | ||
map.put(6, 0); | ||
|
||
map.get(1); | ||
map.put(3, 0); | ||
|
||
assertThat(map.keySet()).containsOnly(3, 1, 6); | ||
|
||
map.get(1); | ||
map.put(4, 0); | ||
|
||
assertThat(map.keySet()).contains(4, 1).hasSize(3); | ||
} | ||
|
||
@Test | ||
public void testSizeEviction() throws InterruptedException { | ||
Cache<Integer, Integer> map = new LFUCacheMap<Integer, Integer>(2, 0, 0); | ||
map.put(1, 0); | ||
map.put(2, 0); | ||
|
||
assertThat(map.keySet()).containsOnly(1, 2); | ||
|
||
map.put(3, 0); | ||
|
||
assertThat(map.keySet()).contains(3).hasSize(2); | ||
|
||
map.put(4, 0); | ||
|
||
assertThat(map.keySet()).contains(4).hasSize(2); | ||
|
||
map.put(5, 0); | ||
|
||
assertThat(map.keySet()).contains(5).hasSize(2); | ||
} | ||
|
||
} |
Oops, something went wrong.