├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── spring2go
│ ├── bigcache
│ ├── BigCache.java
│ ├── BigCacheStats.java
│ ├── CacheConfig.java
│ ├── CacheValueWrapper.java
│ ├── ICache.java
│ ├── lock
│ │ └── StripedReadWriteLock.java
│ ├── storage
│ │ ├── AlignOffHeapStorage.java
│ │ ├── FileChannelStorage.java
│ │ ├── IStorage.java
│ │ ├── IStorageBlock.java
│ │ ├── MemoryMappedStorage.java
│ │ ├── OffHeapStorage.java
│ │ ├── Pointer.java
│ │ ├── StorageBlock.java
│ │ └── StorageManager.java
│ └── utils
│ │ └── FileUtil.java
│ ├── lrucache
│ ├── v1
│ │ └── LruCacheV1.java
│ ├── v2
│ │ └── LruCacheV2.java
│ └── v3
│ │ └── LruCacheV3.java
│ └── okcache
│ ├── AbstractDeque.java
│ ├── BehindStore.java
│ ├── Cache.java
│ ├── CacheBuilder.java
│ ├── CacheEntry.java
│ ├── CacheEntryHelper.java
│ ├── CacheStats.java
│ ├── CacheStatsCounter.java
│ ├── CacheStore.java
│ ├── DummyCacheStore.java
│ ├── ExpirationPolicy.java
│ ├── ICache.java
│ ├── JavaSerializer.java
│ ├── Serializer.java
│ ├── TimeHelper.java
│ ├── impl
│ ├── CacheImpl.java
│ ├── CacheSegment.java
│ ├── HashCacheEntry.java
│ ├── SegmentAccessQueue.java
│ └── SegmentStatsCounter.java
│ └── store
│ └── BigCacheStore.java
└── test
└── java
└── com
└── spring2go
├── bigcache
├── BigCacheCleanerTest.java
├── BigCacheLimitTest.java
├── BigCachePerfTestA.java
├── BigCachePerfTestB.java
├── BigCacheReadWriteStressTest.java
├── BigCacheStressTest.java
├── BigCacheTest.java
├── BigCacheUnitTest.java
├── storage
│ ├── FileChannelStorageTest.java
│ ├── StorageBlockTest.java
│ ├── StorageManagerTest.java
│ └── StorageUnitTest.java
└── utils
│ ├── TestSample.java
│ └── TestUtil.java
├── lrucache
├── v1
│ └── LruCacheV1Test.java
├── v2
│ ├── LruCacheV2PerfTest.java
│ └── LruCacheV2Test.java
└── v3
│ ├── LruCacheV3PerfTest.java
│ └── LruCacheV3Test.java
└── okcache
├── CacheImplTest.java
├── CachePerfGetsTest.java
├── CachePerfTest.java
├── CacheWithEvictStoreTest.java
└── JavaSerializationTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
3 | ### IntelliJ IDEA ###
4 | .idea
5 | *.iws
6 | *.iml
7 | *.ipr
8 |
9 | # Eclipse IDE metadata
10 | .classpath
11 | .project
12 | .settings/
13 |
14 | # Mac files
15 | .DS_Store
16 | /.idea/
17 | dltestDB:/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 波波微课
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OkCache
2 | 一个高性能二级缓存实现, 内存LRU缓存 + 磁盘文件持久化缓存。
3 |
4 | * 支持过期(Expiration)清除;
5 | * 支持LRU ~ 如果超过内存缓存容量,最近不常使用的项将被剔除(Eviction);
6 | * 支持剔除(Eviction)到二级可持久化缓存(BigCache);
7 | * 支持回写(Write Behind)到后端持久化存储,例如DB。
8 | * BigCache的Key常驻内存,Value可持久化。
9 | * BigCache支持纯磁盘文件,内存映射+磁盘文件,和堆外内存+磁盘文件三种模式。
10 |
11 | ## 注意
12 |
13 | 1. 运行BigCache单元测试前,请将TestUtil.TEST_BASE_DIR修改为本地测试目录:
14 | 2. 回写到MySQL的代码未开源,需要的自己实现`BehindStorage`接口即可。
15 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 | com.spring2go
7 | okcache
8 | 1.0-SNAPSHOT
9 |
10 |
11 | 1.8
12 | 1.8
13 |
14 |
15 |
16 |
17 | junit
18 | junit
19 | 4.13
20 | test
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/BigCacheStats.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | /**
4 | * Created on Jul, 2020 by @author bobo
5 | */
6 | public class BigCacheStats {
7 | private final long cacheHit;
8 | private final long cacheMiss;
9 |
10 | private final long cacheGet;
11 | private final long cachePut;
12 | private final long cacheDelete;
13 |
14 | private final long cacheExpire;
15 | private final long cacheMove;
16 | private final long cacheTotalEntries;
17 |
18 | private final long storageUsed;
19 | private final long storageDirty;
20 | private final long storageCapacity;
21 | private final long storageUsedBlocks;
22 | private final long storageFreeBlocks;
23 | private final long storageTotalBlocks;
24 |
25 | public BigCacheStats(long cacheHit, long cacheMiss, long cacheGet,
26 | long cachePut, long cacheDelete, long cacheExpire,
27 | long cacheMove, long cacheTotalEntries, long storageUsed,
28 | long storageDirty, long storageCapacity,
29 | long storageUsedBlocks, long storageFreeBlocks, long storageTotalBlocks) {
30 | this.cacheHit = cacheHit;
31 | this.cacheMiss = cacheMiss;
32 |
33 | this.cacheGet = cacheGet;
34 | this.cachePut = cachePut;
35 | this.cacheDelete = cacheDelete;
36 |
37 | this.cacheExpire = cacheExpire;
38 | this.cacheMove = cacheMove;
39 |
40 | this.cacheTotalEntries = cacheTotalEntries;
41 |
42 | this.storageUsed = storageUsed;
43 | this.storageDirty = storageDirty;
44 | this.storageCapacity = storageCapacity;
45 |
46 | this.storageUsedBlocks = storageUsedBlocks;
47 | this.storageFreeBlocks = storageFreeBlocks;
48 | this.storageTotalBlocks = storageTotalBlocks;
49 | }
50 |
51 | public BigCacheStats() {
52 | this(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L);
53 | }
54 |
55 | public BigCacheStats getDeltaStats(BigCacheStats previousStats) {
56 | if (previousStats == null) {
57 | return this;
58 | }
59 | return new BigCacheStats(
60 | /*$(current value - previous value): the delta between two adjacent stats*/
61 | this.cacheHit - previousStats.cacheHit,
62 | this.cacheMiss - previousStats.cacheMiss,
63 | this.cacheGet - previousStats.cacheGet,
64 | this.cachePut - previousStats.cachePut,
65 | this.cacheDelete - previousStats.cacheDelete,
66 | this.cacheExpire - previousStats.cacheExpire,
67 | this.cacheMove - previousStats.cacheMove,
68 |
69 | /*$(current value): latest value which is more meaningful*/
70 | this.cacheTotalEntries,
71 | this.storageUsed,
72 | this.storageDirty,
73 | this.storageCapacity,
74 | this.storageUsedBlocks,
75 | this.storageFreeBlocks,
76 | this.storageTotalBlocks
77 | );
78 | }
79 |
80 | public long getCacheHit() {
81 | return cacheHit;
82 | }
83 |
84 | public long getCacheMiss() {
85 | return cacheMiss;
86 | }
87 |
88 | public long getCacheGet() {
89 | return cacheGet;
90 | }
91 |
92 | public long getCachePut() {
93 | return cachePut;
94 | }
95 |
96 | public long getCacheDelete() {
97 | return cacheDelete;
98 | }
99 |
100 | public long getCacheExpire() {
101 | return cacheExpire;
102 | }
103 |
104 | public long getCacheMove() {
105 | return cacheMove;
106 | }
107 |
108 | public long getCacheTotalEntries() {
109 | return cacheTotalEntries;
110 | }
111 |
112 | public long getStorageUsed() {
113 | return storageUsed;
114 | }
115 |
116 | public long getStorageDirty() {
117 | return storageDirty;
118 | }
119 |
120 | public long getStorageCapacity() {
121 | return storageCapacity;
122 | }
123 |
124 | public long getStorageUsedBlocks() {
125 | return storageUsedBlocks;
126 | }
127 |
128 | public long getStorageFreeBlocks() {
129 | return storageFreeBlocks;
130 | }
131 |
132 | public long getStorageTotalBlocks() {
133 | return storageTotalBlocks;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/CacheConfig.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import com.spring2go.bigcache.storage.StorageManager;
4 |
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class CacheConfig {
10 | private int concurrencyLevel = BigCache.DEFAULT_CONCURRENCY_LEVEL;
11 | private int capacityPerBlock = StorageManager.DEFAULT_CAPACITY_PER_BLOCK;
12 | private int initialNumberOfBlocks = StorageManager.DEFAULT_INITIAL_NUMBER_OF_BLOCKS;
13 | private long purgeInterval = BigCache.DEFAULT_PURGE_INTERVAL;
14 | private long mergeInterval = BigCache.DEFAULT_MERGE_INTERVAL;
15 | private double dirtyRatioThreshold = BigCache.DEFAULT_DIRTY_RATIO_THRESHOLD;
16 | private long maxOffHeapMemorySize = StorageManager.DEFAULT_MAX_OFFHEAP_MEMORY_SIZE;
17 | private StorageMode storageMode = StorageMode.PureFile;
18 |
19 | public int getConcurrencyLevel() {
20 | return concurrencyLevel;
21 | }
22 |
23 | public CacheConfig setConcurrencyLevel(int concurrencyLevel) {
24 | if(concurrencyLevel > 11 || concurrencyLevel < 0){
25 | throw new IllegalArgumentException("concurrencyLevel must be between 0 and 11 inclusive!");
26 | }
27 |
28 | this.concurrencyLevel = concurrencyLevel;
29 | return this;
30 | }
31 |
32 | public int getCapacityPerBlock() {
33 | return capacityPerBlock;
34 | }
35 |
36 | public CacheConfig setCapacityPerBlock(int capacityPerBlock) {
37 | if(capacityPerBlock < 16 * 1024 * 1024){
38 | throw new IllegalArgumentException("capacityPerBlock must be bigger than 16MB!");
39 | }
40 |
41 | this.capacityPerBlock = capacityPerBlock;
42 | return this;
43 | }
44 |
45 | public int getInitialNumberOfBlocks() {
46 | return initialNumberOfBlocks;
47 | }
48 |
49 | public CacheConfig setInitialNumberOfBlocks(int initialNumberOfBlocks) {
50 |
51 | if(initialNumberOfBlocks <= 0){
52 | throw new IllegalArgumentException("initialNumberOfBlocks must be > 0!");
53 | }
54 |
55 | this.initialNumberOfBlocks = initialNumberOfBlocks;
56 | return this;
57 | }
58 |
59 | public long getPurgeInterval() {
60 | return purgeInterval;
61 | }
62 |
63 | public CacheConfig setPurgeInterval(long purgeInterval) {
64 | this.purgeInterval = purgeInterval;
65 | return this;
66 | }
67 |
68 | public long getMergeInterval() {
69 | return mergeInterval;
70 | }
71 |
72 | public CacheConfig setMergeInterval(long mergeInterval) {
73 | this.mergeInterval = mergeInterval;
74 | return this;
75 | }
76 |
77 | public double getDirtyRatioThreshold() {
78 | return dirtyRatioThreshold;
79 | }
80 |
81 | public CacheConfig setDirtyRatioLimit(double dirtyRatioThreshold) {
82 | this.dirtyRatioThreshold = dirtyRatioThreshold;
83 | return this;
84 | }
85 |
86 | public StorageMode getStorageMode() {
87 | return storageMode;
88 | }
89 |
90 | public CacheConfig setStorageMode(StorageMode storageMode) {
91 | this.storageMode = storageMode;
92 | return this;
93 | }
94 |
95 | /**
96 | * Limiting Offheap memory usage.
97 | *
98 | * Only takes effect when the {@link StorageMode} is set to MemoryMappedPlusFile or OffHeapPlusFile mode,
99 | * in these cases, this setting limits the max offheap memory size.
100 | *
101 | * @param maxOffHeapMemorySize max offheap memory size allowed, unit : byte.
102 | * @return CacheConfig
103 | */
104 | public CacheConfig setMaxOffHeapMemorySize(long maxOffHeapMemorySize) {
105 | if (maxOffHeapMemorySize < this.capacityPerBlock) {
106 | throw new IllegalArgumentException("maxOffHeapMemorySize must be equal to or larger than capacityPerBlock" + this.getCapacityPerBlock());
107 | }
108 | this.maxOffHeapMemorySize = maxOffHeapMemorySize;
109 | return this;
110 | }
111 |
112 | public long getMaxOffHeapMemorySize() {
113 | return this.maxOffHeapMemorySize;
114 | }
115 |
116 | public enum StorageMode {
117 | PureFile,
118 | MemoryMappedPlusFile,
119 | OffHeapPlusFile,
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/CacheValueWrapper.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import com.spring2go.bigcache.storage.Pointer;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | *
8 | * Wrapper class in BigCache, which contains info on access time, ttl and storage.
9 | *
10 | * The {@link BigCache} will protect the r/w operation on this object by two means:
11 | * 1. use a striped write lock in its write operations.
12 | * 2. synchronize the CacheValueWrapper object in the read operations of Cache, as there may be
13 | * multiple threads working on this simultaneously.
14 | */
15 | public class CacheValueWrapper {
16 | /**
17 | * The backend storage info of this entry.
18 | *
19 | */
20 | protected Pointer pointer;
21 |
22 | /**
23 | * The access time in milliseconds.
24 | *
25 | * Read/write of this field should be guarded by locks.
26 | */
27 | protected long lastAccessTime = -1; // -1 means for not initialized.
28 |
29 | /** Time to idle in milliseconds */
30 | protected long timeToIdle = -1L;
31 |
32 | /**
33 | * Gets the last access time.
34 | *
35 | * @return the access time
36 | */
37 | public long getLastAccessTime() {
38 | return lastAccessTime;
39 | }
40 |
41 | public CacheValueWrapper(Pointer pointer, long lastAccessTime, long timeToIdle) {
42 | this.pointer = pointer;
43 | this.lastAccessTime = lastAccessTime;
44 | this.timeToIdle = timeToIdle;
45 | }
46 |
47 | public CacheValueWrapper() {
48 | }
49 |
50 | /**
51 | * Sets the last access time.
52 | *
53 | * we need to put the following restriction:
54 | * 1. We will always set the time to a bigger value than the current one.
55 | * 2. If the pointer has expired, don't set the value, so there won't be expire to non-expire, which
56 | * is wrong
57 | *
58 | * @param accessTime the new access time
59 | * @return true if we has modified the time successfully.
60 | */
61 | public void setLastAccessTime(long accessTime) {
62 | if (lastAccessTime < 0) {
63 | // not initialized yet.
64 | lastAccessTime = accessTime;
65 | return;
66 | }
67 |
68 | // don't set it to an old value
69 | if (lastAccessTime >= accessTime) return;
70 |
71 | // can't update the access value if it has already expired.
72 | if (isExpired()) return;
73 |
74 | lastAccessTime = accessTime;
75 | }
76 |
77 | /**
78 | * Gets the time to idle in milliseconds
79 | *
80 | * @return time to idle
81 | */
82 | public long getTimeToIdle() {
83 | return timeToIdle;
84 | }
85 |
86 | /**
87 | * Sets the time to idle in milliseconds
88 | *
89 | * @param timeToIdle the new time to idle
90 | */
91 | public void setTimeToIdle(long timeToIdle) {
92 | this.timeToIdle = timeToIdle;
93 | }
94 |
95 | public Pointer getPointer() {
96 | return pointer;
97 | }
98 |
99 | public void setPointer(Pointer pointer) {
100 | this.pointer = pointer;
101 | }
102 |
103 | /**
104 | * Is the cached item expired
105 | *
106 | * @return expired or not
107 | */
108 | public boolean isExpired() {
109 | if (this.timeToIdle <= 0) return false; // never expire
110 | if (this.lastAccessTime < 0) return false; // not initialized
111 | return System.currentTimeMillis() - this.lastAccessTime > this.timeToIdle;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/ICache.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | *
9 | * The Interface ICache.
10 | *
11 | * @param the key type
12 | */
13 | public interface ICache extends Closeable {
14 | /**
15 | * Puts the value with the specified key.
16 | *
17 | * @param key the key
18 | * @param value the value
19 | * @throws IOException
20 | */
21 | void put(K key, byte[] value) throws IOException;
22 |
23 | /**
24 | * Puts the value with specified key and time to idle in milliseconds.
25 | *
26 | * @param key the key
27 | * @param value the value
28 | * @param ttl the time to idle value in milliseconds
29 | * @throws IOException
30 | */
31 | void put(K key, byte[] value, long tti) throws IOException;
32 |
33 | /**
34 | * Gets the value with the specified key.
35 | *
36 | * @param key the key
37 | * @return the value
38 | * @throws IOException
39 | */
40 | byte[] get(K key) throws IOException;
41 |
42 | /**
43 | * Delete the value with the specified key.
44 | *
45 | * @param key the key
46 | * @return the value
47 | * @throws IOException
48 | */
49 | byte[] delete(K key) throws IOException;
50 |
51 | /**
52 | * Check if Cache contains the specified key.
53 | *
54 | * @param key the key
55 | * @return true, if successful
56 | */
57 | boolean contains(K key);
58 |
59 | /**
60 | * Clear the cache.
61 | */
62 | void clear();
63 |
64 | /**
65 | * Calculates the Hit ratio.
66 | *
67 | * @return the double
68 | */
69 | double hitRatio();
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/lock/StripedReadWriteLock.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.lock;
2 |
3 | import java.util.concurrent.locks.ReentrantReadWriteLock;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | *
8 | * The StripedReadWriteLock is lock holder that contains array of
9 | * {@link java.util.concurrent.locks.ReentrantReadWriteLock}, on which lock/unlock
10 | * operations are performed. Purpose of this is to decrease lock contention.
11 | * When a lock requested, this lock gives a lock associated with the given id.
12 | */
13 | public class StripedReadWriteLock {
14 |
15 | private final ReentrantReadWriteLock[] locks;
16 |
17 | /**
18 | * Default factor, creates 16 locks
19 | */
20 | public StripedReadWriteLock() {
21 | this(4);
22 | }
23 |
24 | /**
25 | * Creates array of locks, size of array may be any from set {2^1, 2^2, ..., 2^11}
26 | *
27 | * @param storagePower size of array will be equal to 2^storagePower
28 | */
29 | public StripedReadWriteLock(int storagePower) {
30 | if (!(storagePower >= 1 && storagePower <= 11)) {
31 | throw new IllegalArgumentException("storage power must be in {1..11}");
32 | }
33 |
34 | int lockSize = (int) Math.pow(2, storagePower);
35 | locks = new ReentrantReadWriteLock[lockSize];
36 | for (int i = 0; i < locks.length; i++){
37 | locks[i] = new ReentrantReadWriteLock();
38 | }
39 | }
40 |
41 | /**
42 | * Locks lock associated with given id.
43 | *
44 | * @param id value, from which lock is derived
45 | */
46 | public void readLock(int id) {
47 | getLock(id).readLock().lock();
48 | }
49 |
50 | /**
51 | * Unlocks lock associated with given id.
52 | *
53 | * @param id value, from which lock is derived
54 | */
55 | public void readUnlock(int id) {
56 | getLock(id).readLock().unlock();
57 | }
58 |
59 | /**
60 | * Locks lock associated with given id.
61 | *
62 | * @param id value, from which lock is derived
63 | */
64 | public void writeLock(int id) {
65 | getLock(id).writeLock().lock();
66 | }
67 |
68 | /**
69 | * Unlocks lock associated with given id.
70 | *
71 | * @param id value, from which lock is derived
72 | */
73 | public void writeUnlock(int id) {
74 | getLock(id).writeLock().unlock();
75 | }
76 |
77 | /**
78 | * Locks all locks as write lock.
79 | */
80 | public void writeLockForAll() {
81 | for (int i = 0; i < locks.length; i++) {
82 | getLock(i).writeLock().lock();
83 | }
84 | }
85 |
86 | /**
87 | * Unlocks all locks as write lock.
88 | */
89 | public void writeUnlockForAll() {
90 | for (int i = 0; i < locks.length; i++) {
91 | getLock(i).writeLock().unlock();
92 | }
93 | }
94 |
95 | /**
96 | * Finds the lock associated with the id
97 | *
98 | * @param id value, from which lock is derived
99 | * @return lock which is associated with the id
100 | */
101 | public ReentrantReadWriteLock getLock(int id) {
102 | // locks.length-1 is a string of ones since lock.length is power of 2,
103 | // thus ending cancels out the higher bits of id and leaves the lower bits
104 | // to determine the lock.
105 | return locks[id & (locks.length - 1)];
106 | }
107 | }
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/AlignOffHeapStorage.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.IOException;
4 | import java.lang.reflect.Field;
5 | import java.nio.ByteBuffer;
6 |
7 | /**
8 | * Created on Jul, 2020 by @author bobo
9 | *
10 | * This class is called when buffers should be allocated aligned.
11 | *
12 | * It creates a block of memory aligned to memory page size, and limits the amount of buffer each processor can access.
13 | *
14 | */
15 | public class AlignOffHeapStorage extends OffHeapStorage {
16 | public AlignOffHeapStorage(int capacity) {
17 | super(capacity, ByteBuffer.allocateDirect(capacity));
18 | }
19 |
20 | @Override
21 | public void close() throws IOException {
22 | if (!disposed.compareAndSet(false, true))
23 | return;
24 | if (byteBuffer == null)
25 | return;
26 | try {
27 | Field cleanerField = byteBuffer.getClass().getDeclaredField("cleaner");
28 | cleanerField.setAccessible(true);
29 | sun.misc.Cleaner cleaner = (sun.misc.Cleaner) cleanerField.get(byteBuffer);
30 | cleaner.clean();
31 | } catch (Exception e) {
32 | throw new Error(e);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/FileChannelStorage.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.nio.ByteBuffer;
7 | import java.nio.channels.FileChannel;
8 |
9 | /**
10 | * Created on Jul, 2020 by @author bobo
11 | */
12 | public class FileChannelStorage implements IStorage {
13 | private FileChannel fileChannel;
14 | private RandomAccessFile raf;
15 |
16 | public FileChannelStorage(String dir, int index, int capacity) throws IOException {
17 | File dirFile = new File(dir);
18 | if (!dirFile.exists()) { dirFile.mkdirs(); }
19 | String fullFileName = dir + index + "-" + System.currentTimeMillis() + DATA_FILE_SUFFIX;
20 | raf = new RandomAccessFile(fullFileName, "rw");
21 | raf.setLength(capacity);
22 | fileChannel = raf.getChannel();
23 | }
24 |
25 | @Override
26 | public void get(int position, byte[] dest) throws IOException {
27 | fileChannel.read(ByteBuffer.wrap(dest), position);
28 | }
29 |
30 | @Override
31 | public void put(int position, byte[] source) throws IOException {
32 | fileChannel.write(ByteBuffer.wrap(source), position);
33 | }
34 |
35 | @Override
36 | public void free() {
37 | // nothing to do here
38 | }
39 |
40 | @Override
41 | public void close() throws IOException {
42 | if (this.fileChannel != null) {
43 | this.fileChannel.close();
44 | }
45 | if (this.raf != null) {
46 | this.raf.close();
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/IStorage.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | *
9 | * The Interface IStorage for get/put cached data in bytes.
10 | */
11 | public interface IStorage extends Closeable {
12 | public static final String DATA_FILE_SUFFIX = ".data";
13 |
14 | /**
15 | * Gets bytes from the specified location.
16 | *
17 | * @param position the position
18 | * @param dest the destination
19 | */
20 | void get(int position, byte[] dest) throws IOException;
21 |
22 | /**
23 | * Puts source to the specified location of the Storage.
24 | *
25 | * @param position the position
26 | * @param source the source
27 | */
28 | void put(int position, byte[] source) throws IOException;
29 |
30 | /**
31 | * Frees the storage.
32 | */
33 | void free();
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/IStorageBlock.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | *
9 | * The Interface IStorageBlock.
10 | *
11 | * A storage unit with fixed capacity.
12 | */
13 | public interface IStorageBlock extends Comparable, Closeable {
14 | /**
15 | * Retrieves the payload associated with the pointer and always update the access time.
16 | *
17 | * @param pointer the pointer
18 | * @return the byte[]
19 | * @throws IOException
20 | */
21 | byte[] retrieve(Pointer pointer) throws IOException;
22 |
23 | /**
24 | * Removes the payload and marks the used space as dirty.
25 | *
26 | * @param pointer the pointer
27 | * @return the byte[]
28 | * @throws IOException
29 | */
30 | byte[] remove(Pointer pointer) throws IOException;
31 |
32 | /**
33 | * Removes the payload without returning the payload
34 | *
35 | * @param pointer the pointer
36 | * @throws IOException
37 | */
38 | void removeLight(Pointer pointer) throws IOException;
39 |
40 | /**
41 | * Stores the payload.
42 | *
43 | * @param payload the payload
44 | * @return the pointer
45 | * @throws IOException
46 | */
47 | Pointer store(byte[] payload) throws IOException;
48 |
49 | /**
50 | * Updates the payload by marking exSpace as dirty.
51 | *
52 | * @param pointer the pointer
53 | * @param payload the payload
54 | * @return the pointer
55 | * @throws IOException
56 | */
57 | Pointer update(Pointer pointer, byte[] payload) throws IOException;
58 |
59 | /**
60 | * Calculates and returns total size of the dirty space.
61 | *
62 | * @return the total size of the dirty space.
63 | */
64 | long getDirty();
65 |
66 | /**
67 | * Calculates and returns total size of the used space.
68 | *
69 | * @return the total size of the used space.
70 | */
71 | long getUsed();
72 |
73 | /**
74 | * Calculates and returns total capacity of the block.
75 | *
76 | * @return the total capacity of the block.
77 | */
78 | long getCapacity();
79 |
80 | /**
81 | * Calculates and returns the dirty to capacity ratio
82 | *
83 | * @return dirty ratio
84 | */
85 | double getDirtyRatio();
86 |
87 | /**
88 | * Get the index of this storage block
89 | *
90 | * @return an index
91 | */
92 | int getIndex();
93 |
94 | /**
95 | * Frees the storage.
96 | */
97 | void free();
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/MemoryMappedStorage.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.nio.ByteBuffer;
7 | import java.nio.MappedByteBuffer;
8 | import java.nio.channels.FileChannel;
9 |
10 | /**
11 | * Created on Jul, 2020 by @author bobo
12 | */
13 | public class MemoryMappedStorage implements IStorage {
14 | private RandomAccessFile raf;
15 | private ThreadLocalByteBuffer threadLocalBuffer;
16 |
17 | public MemoryMappedStorage(String dir, int index, int capacity) throws IOException {
18 | File backFile = new File(dir);
19 | if (!backFile.exists()) {
20 | backFile.mkdirs();
21 | }
22 | String backFileName = dir + index + "-" + System.currentTimeMillis() + DATA_FILE_SUFFIX;
23 | raf = new RandomAccessFile(backFileName, "rw");
24 | MappedByteBuffer mappedByteBuffer = raf.getChannel().map(FileChannel.MapMode.PRIVATE, 0, capacity);
25 | threadLocalBuffer = new ThreadLocalByteBuffer(mappedByteBuffer);
26 | }
27 |
28 | private ByteBuffer getLocal(int position) {
29 | ByteBuffer buffer = threadLocalBuffer.get();
30 | buffer.position(position);
31 | return buffer;
32 | }
33 |
34 | @Override
35 | public void close() throws IOException {
36 | if (raf != null) {
37 | raf.close();
38 | }
39 | //implies system GC
40 | threadLocalBuffer.set(null);
41 | threadLocalBuffer = null;
42 | }
43 |
44 | @Override
45 | public void get(int position, byte[] dest) throws IOException {
46 | ByteBuffer buffer = this.getLocal(position);
47 | buffer.get(dest);
48 | }
49 |
50 | @Override
51 | public void put(int position, byte[] source) throws IOException {
52 | ByteBuffer buffer = this.getLocal(position);
53 | buffer.put(source);
54 | }
55 |
56 | @Override
57 | public void free() {
58 | MappedByteBuffer buffer = (MappedByteBuffer) threadLocalBuffer.getSourceBuffer();
59 | buffer.clear();
60 | }
61 |
62 | private static class ThreadLocalByteBuffer extends ThreadLocal {
63 | private ByteBuffer _src;
64 |
65 | public ThreadLocalByteBuffer(ByteBuffer src) {
66 | _src = src;
67 | }
68 |
69 | public ByteBuffer getSourceBuffer() {
70 | return _src;
71 | }
72 |
73 | @Override
74 | protected synchronized ByteBuffer initialValue() {
75 | ByteBuffer dup = _src.duplicate();
76 | return dup;
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/OffHeapStorage.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import sun.misc.Unsafe;
4 |
5 | import java.io.IOException;
6 | import java.lang.reflect.Field;
7 | import java.lang.reflect.Method;
8 | import java.nio.ByteBuffer;
9 | import java.util.concurrent.atomic.AtomicBoolean;
10 |
11 | /**
12 | * Created on Jul, 2020 by @author bobo
13 | */
14 | public class OffHeapStorage implements IStorage {
15 | protected final AtomicBoolean disposed = new AtomicBoolean(false);
16 | protected ByteBuffer byteBuffer;
17 |
18 | private static final Unsafe UNSAFE = getUnsafe();
19 | private static final long BYTE_ARRAY_OFFSET = (long) UNSAFE.arrayBaseOffset(byte[].class);
20 |
21 | private final long address;
22 |
23 | private static Unsafe getUnsafe() {
24 | try {
25 | Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
26 | unsafeField.setAccessible(true);
27 | return (sun.misc.Unsafe) unsafeField.get(null);
28 | } catch (Exception e) {
29 | throw new RuntimeException(e);
30 | }
31 | }
32 |
33 | public OffHeapStorage(int capacity) {
34 | this.address = UNSAFE.allocateMemory(capacity);
35 | }
36 |
37 | public OffHeapStorage(int capacity, ByteBuffer buffer) {
38 | this.byteBuffer = ByteBuffer.allocateDirect(capacity);
39 | try {
40 | Method method = byteBuffer.getClass().getDeclaredMethod("address");
41 | method.setAccessible(true);
42 | this.address = (Long) method.invoke(byteBuffer);
43 | } catch (Exception e) {
44 | throw new RuntimeException("Unable to allocate offheap memory using sun.misc.Unsafe on your platform", e);
45 | }
46 | }
47 |
48 | @Override
49 | public void close() throws IOException {
50 | if (!disposed.compareAndSet(false, true))
51 | return;
52 | UNSAFE.freeMemory(address);
53 | }
54 |
55 | @Override
56 | public void get(int position, byte[] dest) throws IOException {
57 | assert !disposed.get() : "disposed";
58 | assert position >= 0 : position;
59 | this.get(address + position, dest, BYTE_ARRAY_OFFSET, dest.length);
60 | }
61 |
62 | /**
63 | * Get bytes from the local buffer to a given byte array.
64 | *
65 | * @param baseAddress the absolute base address of the local buffer
66 | * @param dest the dest byte array
67 | * @param destOffset the offset of the dest byte array
68 | * @param length the length of bytes to get
69 | */
70 | private void get(long baseAddress, byte[] dest, long destOffset, long length) {
71 | UNSAFE.copyMemory(null, baseAddress, dest, destOffset, length);
72 | }
73 |
74 | @Override
75 | public void put(int position, byte[] source) throws IOException {
76 | assert !disposed.get() : "disposed";
77 | assert position >= 0 : position;
78 | this.put(BYTE_ARRAY_OFFSET, source, address + position, source.length);
79 |
80 | }
81 |
82 | /**
83 | * Put bytes from a given byte array to the local buffer.
84 | *
85 | * @param srcOffset the offset of the source byte array
86 | * @param source the source byte array
87 | * @param baseAddress the absolute base address of the local buffer
88 | * @param length the length of bytes to put
89 | */
90 | private void put(long srcOffset, byte[] source, long baseAddress, long length) {
91 | UNSAFE.copyMemory(source, srcOffset, null, baseAddress, length);
92 | }
93 |
94 | @Override
95 | public void free() {
96 | //do nothing
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/Pointer.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | /**
4 | * Created on Jul, 2020 by @author bobo
5 | *
6 | * The Class Pointer is a pointer to the stored cache data, which keeps
7 | * position and length of the payload and associated StorageBlock.
8 | */
9 | public class Pointer {
10 | /** The position. */
11 | protected int position;
12 |
13 | /** The length of the value. */
14 | protected int length;
15 |
16 | /** The associated storage block. */
17 | protected StorageBlock storageBlock;
18 |
19 | /**
20 | * Instantiates a new pointer.
21 | *
22 | * @param position the position
23 | * @param length the length of the value
24 | * @param storageBlock the persistent cache storage
25 | */
26 | public Pointer(int position, int length, StorageBlock storageBlock) {
27 | this.position = position;
28 | this.length = length;
29 | this.storageBlock = storageBlock;
30 | }
31 |
32 | /**
33 | * Gets the position.
34 | *
35 | * @return the position
36 | */
37 | public int getPosition() {
38 | return position;
39 | }
40 |
41 | /**
42 | * Sets the position.
43 | *
44 | * @param position the new position
45 | */
46 | public void setPosition(int position) {
47 | this.position = position;
48 | }
49 |
50 | /**
51 | * Gets the storage block.
52 | *
53 | * @return the storage block.
54 | */
55 | public StorageBlock getStorageBlock() {
56 | return storageBlock;
57 | }
58 |
59 | /**
60 | * Sets the storage block.
61 | *
62 | * @param storageBlock the new storage block.
63 | */
64 | public void setStorageBlock(StorageBlock storageBlock) {
65 | this.storageBlock = storageBlock;
66 | }
67 |
68 | /**
69 | * Gets the length of the value
70 | *
71 | * @return the length of the stored value
72 | */
73 | public int getLength() {
74 | return length;
75 | }
76 |
77 | /**
78 | * Sets the length of the stored value.
79 | *
80 | * @param length the length of the stored value.
81 | */
82 | public void setLength(int length) {
83 | this.length = length;
84 | }
85 |
86 |
87 | /**
88 | * Copies given pointer.
89 | *
90 | * @param pointer the pointer
91 | * @return the pointer
92 | */
93 | public Pointer copy(Pointer pointer) {
94 | this.position = pointer.position;
95 | this.length = pointer.length;
96 | this.storageBlock = pointer.storageBlock;
97 | return this;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/StorageBlock.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.IOException;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | import com.spring2go.bigcache.CacheConfig.StorageMode;
7 |
8 | /**
9 | * Created on Jul, 2020 by @author bobo
10 | *
11 | * The Class StorageBlock.
12 | */
13 | public class StorageBlock implements IStorageBlock {
14 | /** The index. */
15 | private final int index;
16 |
17 | /** The capacity. */
18 | private final int capacity;
19 |
20 | /** The underlying storage. */
21 | private IStorage underlyingStorage;
22 |
23 | /** The offset within the storage block. */
24 | private final AtomicInteger currentOffset = new AtomicInteger(0);
25 |
26 | /** The dirty storage. */
27 | private final AtomicInteger dirtyStorage = new AtomicInteger(0);
28 |
29 | /** The used storage. */
30 | private final AtomicInteger usedStorage = new AtomicInteger(0);
31 |
32 | /**
33 | * Instantiates a new storage block.
34 | *
35 | * @param dir the directory
36 | * @param index the index
37 | * @param capacity the capacity
38 | * @throws IOException exception throws when failing to create the storage block
39 | */
40 | public StorageBlock(String dir, int index, int capacity, StorageMode storageMode) throws IOException{
41 | this.index = index;
42 | this.capacity = capacity;
43 | switch (storageMode) {
44 | case PureFile:
45 | underlyingStorage = new FileChannelStorage(dir, index, capacity);
46 | break;
47 | case MemoryMappedPlusFile:
48 | underlyingStorage = new MemoryMappedStorage(dir, index, capacity);
49 | break;
50 | case OffHeapPlusFile:
51 | underlyingStorage = new OffHeapStorage(capacity);
52 | break;
53 | }
54 | }
55 |
56 | @Override
57 | public byte[] retrieve(Pointer pointer) throws IOException {
58 | byte [] payload = new byte[pointer.getLength()];
59 | underlyingStorage.get(pointer.getPosition(), payload);
60 | return payload;
61 | }
62 |
63 | @Override
64 | public byte[] remove(Pointer pointer) throws IOException {
65 | byte [] payload = retrieve(pointer);
66 | dirtyStorage.addAndGet(pointer.getLength());
67 | usedStorage.addAndGet(-1 * pointer.getLength());
68 | return payload;
69 | }
70 |
71 |
72 | @Override
73 | public void removeLight(Pointer pointer) throws IOException {
74 | dirtyStorage.addAndGet(pointer.getLength());
75 | usedStorage.addAndGet(-1 * pointer.getLength());
76 | }
77 |
78 | @Override
79 | public Pointer store(byte[] payload) throws IOException {
80 | Allocation allocation = allocate(payload);
81 | if (allocation == null) return null; // not enough storage available
82 | Pointer pointer = store(allocation, payload);
83 | return pointer;
84 | }
85 |
86 | /**
87 | * Allocates storage for the payload, return null if not enough storage available.
88 | *
89 | * @param payload the payload
90 | * @return the allocation
91 | */
92 | protected Allocation allocate(byte[] payload) {
93 | int payloadLength = payload.length;
94 | int allocationOffset = currentOffset.addAndGet(payloadLength);
95 | if(this.capacity < allocationOffset){
96 | return null;
97 | }
98 | Allocation allocation = new Allocation(allocationOffset - payloadLength, payloadLength);
99 | return allocation;
100 | }
101 |
102 |
103 | /**
104 | * Stores the payload by the help of allocation.
105 | *
106 | * @param allocation the allocation
107 | * @param payload the payload
108 | * @return the pointer
109 | * @throws IOException
110 | */
111 | public Pointer store(Allocation allocation, byte[] payload) throws IOException {
112 | Pointer pointer = new Pointer(allocation.getOffset(), allocation.getLength(), this);
113 | underlyingStorage.put(allocation.getOffset(), payload);
114 | usedStorage.addAndGet(payload.length);
115 | return pointer;
116 | }
117 |
118 | @Override
119 | public Pointer update(Pointer pointer, byte[] payload) throws IOException {
120 | if (pointer.getLength() >= payload.length) { // has enough space to reuse
121 | dirtyStorage.addAndGet(pointer.getLength() - payload.length);
122 | usedStorage.addAndGet(-1 * pointer.getLength());
123 | Allocation allocation = new Allocation(pointer.getPosition(), payload.length);
124 | return store(allocation, payload); // should always return a new pointer
125 | } else { // make a move
126 | dirtyStorage.addAndGet(pointer.getLength());
127 | usedStorage.addAndGet(-1 * pointer.getLength());
128 | return store(payload); // may return null because not enough space available
129 | }
130 | }
131 |
132 | @Override
133 | public long getDirty() {
134 | return this.dirtyStorage.get();
135 | }
136 |
137 | @Override
138 | public double getDirtyRatio() {
139 | return (this.getDirty() * 1.0) / this.getCapacity();
140 | }
141 |
142 | @Override
143 | public long getCapacity() {
144 | return capacity;
145 | }
146 |
147 | @Override
148 | public long getUsed() {
149 | return this.usedStorage.get();
150 | }
151 |
152 | @Override
153 | public void free() {
154 |
155 | currentOffset.set(0);
156 | dirtyStorage.set(0);
157 | usedStorage.set(0);
158 |
159 | underlyingStorage.free();
160 | }
161 |
162 | /**
163 | * The Class Allocation.
164 | */
165 | private static class Allocation {
166 |
167 | /** The offset. */
168 | private int offset;
169 |
170 | /** The length. */
171 | private int length;
172 |
173 | /**
174 | * Instantiates a new allocation.
175 | *
176 | * @param offset the offset
177 | * @param length the length
178 | */
179 | public Allocation(int offset, int length) {
180 | this.offset = offset;
181 | this.length = length;
182 | }
183 |
184 | /**
185 | * Gets the offset.
186 | *
187 | * @return the offset
188 | */
189 | public int getOffset() {
190 | return offset;
191 | }
192 |
193 | /**
194 | * Gets the length.
195 | *
196 | * @return the length
197 | */
198 | public int getLength() {
199 | return length;
200 | }
201 | }
202 |
203 | /**
204 | * Gets the index.
205 | *
206 | * @return the index
207 | */
208 | public int getIndex() {
209 | return index;
210 | }
211 |
212 | @Override
213 | public void close() throws IOException {
214 | if (this.underlyingStorage != null) {
215 | this.underlyingStorage.close();
216 | }
217 | }
218 |
219 | @Override
220 | public int compareTo(IStorageBlock o) {
221 | if (this.getIndex() < o.getIndex()) return -1;
222 | else if (this.getIndex() == o.getIndex()) return 0;
223 | else return 1;
224 | }
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/storage/StorageManager.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import java.io.IOException;
4 | import java.util.HashSet;
5 | import java.util.Iterator;
6 | import java.util.Queue;
7 | import java.util.Set;
8 | import java.util.concurrent.ConcurrentLinkedQueue;
9 | import java.util.concurrent.PriorityBlockingQueue;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 | import java.util.concurrent.locks.Lock;
12 | import java.util.concurrent.locks.ReentrantLock;
13 |
14 | import com.spring2go.bigcache.CacheConfig.StorageMode;
15 |
16 | /**
17 | * Created on Jul, 2020 by @author bobo
18 | *
19 | * Managing a list of used/free storage blocks for cache operations like get/put/delete
20 | *
21 | */
22 | public class StorageManager implements IStorageBlock {
23 | /** keep track of the number of blocks allocated */
24 | private final AtomicInteger blockCount = new AtomicInteger(0);
25 |
26 | /**
27 | * Directory for cache data store
28 | */
29 | private final String dir;
30 |
31 | /**
32 | * The capacity per block in bytes
33 | *
34 | */
35 | private final int capacityPerBlock;
36 |
37 |
38 | /** The active storage block change lock. */
39 | private final Lock activeBlockChangeLock = new ReentrantLock();
40 |
41 | /**
42 | * A list of used storage blocks
43 | */
44 | private final Queue usedBlocks = new ConcurrentLinkedQueue();
45 |
46 | /**
47 | * A queue of free storage blocks which is a priority queue and always return the block with smallest index.
48 | */
49 | private final Queue freeBlocks = new PriorityBlockingQueue();
50 |
51 | /**
52 | * Current active block for appending new cache data
53 | */
54 | private volatile IStorageBlock activeBlock;
55 |
56 | /**
57 | * Current storage mode
58 | */
59 | private final StorageMode storageMode;
60 |
61 | /**
62 | * The number of memory blocks allow to be created.
63 | */
64 | private int allowedOffHeapModeBlockCount;
65 |
66 | /**
67 | * The Constant DEFAULT_CAPACITY_PER_BLOCK.
68 | */
69 | public final static int DEFAULT_CAPACITY_PER_BLOCK = 128 * 1024 * 1024; // 128M
70 |
71 | /** The Constant DEFAULT_INITIAL_NUMBER_OF_BLOCKS. */
72 | public final static int DEFAULT_INITIAL_NUMBER_OF_BLOCKS = 8; // 1GB total
73 |
74 | /**
75 | * The Constant DEFAULT_MEMORY_SIZE.
76 | */
77 | public static final long DEFAULT_MAX_OFFHEAP_MEMORY_SIZE = 2 * 1024 * 1024 * 1024L; //Unit: GB
78 |
79 | public StorageManager(String dir, int capacityPerBlock, int initialNumberOfBlocks, StorageMode storageMode,
80 | long maxOffHeapMemorySize) throws IOException {
81 |
82 | if (storageMode != StorageMode.PureFile) {
83 | this.allowedOffHeapModeBlockCount = (int)(maxOffHeapMemorySize / capacityPerBlock);
84 | } else {
85 | this.allowedOffHeapModeBlockCount = 0;
86 | }
87 | this.storageMode = storageMode;
88 | this.capacityPerBlock = capacityPerBlock;
89 | this.dir = dir;
90 |
91 | for (int i = 0; i < initialNumberOfBlocks; i++) {
92 | IStorageBlock storageBlock = this.createNewBlock(i);
93 | freeBlocks.offer(storageBlock);
94 | }
95 |
96 | this.blockCount.set(initialNumberOfBlocks);
97 | this.activeBlock = freeBlocks.poll();
98 | this.usedBlocks.add(this.activeBlock);
99 |
100 | }
101 |
102 | @Override
103 | public byte[] retrieve(Pointer pointer) throws IOException {
104 | return pointer.getStorageBlock().retrieve(pointer);
105 | }
106 |
107 | @Override
108 | public byte[] remove(Pointer pointer) throws IOException {
109 | return pointer.getStorageBlock().remove(pointer);
110 | }
111 |
112 |
113 | @Override
114 | public void removeLight(Pointer pointer) throws IOException {
115 | pointer.getStorageBlock().removeLight(pointer);
116 | }
117 |
118 | @Override
119 | public Pointer store(byte[] payload) throws IOException {
120 | Pointer pointer = activeBlock.store(payload);
121 | if (pointer != null) return pointer; // success
122 | else { // overflow
123 | activeBlockChangeLock.lock();
124 | try {
125 | // other thread may have changed the active block
126 | pointer = activeBlock.store(payload);
127 | if (pointer != null) return pointer; // success
128 | else { // still overflow
129 | IStorageBlock freeBlock = this.freeBlocks.poll();
130 | if (freeBlock == null) { // create a new one
131 | freeBlock = this.createNewBlock(this.blockCount.getAndIncrement());
132 | }
133 | pointer = freeBlock.store(payload);
134 | this.activeBlock = freeBlock;
135 | this.usedBlocks.add(this.activeBlock);
136 | return pointer;
137 | }
138 |
139 | } finally {
140 | activeBlockChangeLock.unlock();
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * Stores the payload to the free storage block excluding the given block.
147 | *
148 | * @param payload the payload
149 | * @param exludingBlock the storage block to be excluded
150 | * @return the pointer
151 | */
152 | public Pointer storeExcluding(byte[] payload, StorageBlock exludingBlock) throws IOException {
153 | while (this.activeBlock == exludingBlock) {
154 | activeBlockChangeLock.lock();
155 | try {
156 | // other thread may have changed the active block
157 | if (this.activeBlock != exludingBlock) break;
158 | IStorageBlock freeBlock = this.freeBlocks.poll();
159 | if (freeBlock == null) {
160 | freeBlock = this.createNewBlock(this.blockCount.getAndIncrement());
161 | }
162 | this.activeBlock = freeBlock;
163 | this.usedBlocks.add(this.activeBlock);
164 | } finally {
165 | activeBlockChangeLock.unlock();
166 | }
167 | }
168 | return store(payload);
169 | }
170 |
171 | @Override
172 | public Pointer update(Pointer pointer, byte[] payload) throws IOException {
173 | Pointer updatePointer = pointer.getStorageBlock().update(pointer, payload);
174 | if (updatePointer != null) {
175 | return updatePointer;
176 | }
177 | return store(payload);
178 | }
179 |
180 | @Override
181 | public long getDirty() {
182 | long dirtyStorage = 0;
183 | for(IStorageBlock block : usedBlocks) {
184 | dirtyStorage += block.getDirty();
185 | }
186 | return dirtyStorage;
187 | }
188 |
189 | private Set getAllInUsedBlocks() {
190 | Set allBlocks = new HashSet();
191 | allBlocks.addAll(usedBlocks);
192 | allBlocks.addAll(freeBlocks);
193 | return allBlocks;
194 | }
195 |
196 | @Override
197 | public long getCapacity() {
198 | long totalCapacity = 0;
199 | for(IStorageBlock block : getAllInUsedBlocks()) {
200 | totalCapacity += block.getCapacity();
201 | }
202 | return totalCapacity;
203 | }
204 |
205 | @Override
206 | public double getDirtyRatio() {
207 | return (this.getDirty() * 1.0) / this.getCapacity();
208 | }
209 |
210 |
211 | @Override
212 | public long getUsed() {
213 | long usedStorage = 0;
214 | for(IStorageBlock block : usedBlocks) {
215 | usedStorage += block.getUsed();
216 | }
217 | return usedStorage;
218 | }
219 |
220 | @Override
221 | public void free() {
222 | // safe?
223 | for(IStorageBlock storageBlock : usedBlocks) {
224 | storageBlock.free();
225 | this.freeBlocks.offer(storageBlock);
226 | }
227 | usedBlocks.clear();
228 | this.activeBlock = freeBlocks.poll();
229 | this.usedBlocks.add(this.activeBlock);
230 | }
231 |
232 | private IStorageBlock createNewBlock(int index) throws IOException {
233 | if (this.allowedOffHeapModeBlockCount > 0) {
234 | IStorageBlock block = new StorageBlock(this.dir, index, this.capacityPerBlock, this.storageMode);
235 | this.allowedOffHeapModeBlockCount--;
236 | return block;
237 | } else {
238 | return new StorageBlock(this.dir, index, this.capacityPerBlock, StorageMode.PureFile);
239 | }
240 | }
241 |
242 | // only run by one thread.
243 | public void clean() {
244 | synchronized (this) {
245 | Iterator it = usedBlocks.iterator();
246 | while(it.hasNext()) {
247 | IStorageBlock storageBlock = it.next();
248 | if (storageBlock == activeBlock) {
249 | // let active block be cleaned in the next run
250 | continue;
251 | }
252 |
253 | if (storageBlock.getUsed() == 0) {
254 | // we will not allocating memory from it any more and it is used by nobody.
255 | storageBlock.free();
256 | freeBlocks.add(storageBlock);
257 | it.remove();
258 | }
259 | }
260 | }
261 | }
262 |
263 | @Override
264 | public void close() throws IOException {
265 | for(IStorageBlock usedBlock : usedBlocks) {
266 | usedBlock.close();
267 | }
268 | usedBlocks.clear();
269 | for(IStorageBlock freeBlock : freeBlocks) {
270 | freeBlock.close();
271 | }
272 | freeBlocks.clear();
273 | }
274 |
275 | @Override
276 | public int compareTo(IStorageBlock o) {
277 | throw new IllegalStateException("Not implemented!");
278 | }
279 |
280 | @Override
281 | public int getIndex() {
282 | throw new IllegalStateException("Not implemented!");
283 | }
284 |
285 | public int getFreeBlockCount() {
286 | return this.freeBlocks.size();
287 | }
288 |
289 | public int getUsedBlockCount() {
290 | return this.usedBlocks.size();
291 | }
292 |
293 | public int getTotalBlockCount() {
294 | return this.getAllInUsedBlocks().size();
295 | }
296 |
297 | }
298 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/bigcache/utils/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.utils;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 |
8 | /**
9 | * Created on Jul, 2020 by @author bobo
10 | */
11 | public class FileUtil {
12 | private static final int BUFFER_SIZE = 4096 * 4;
13 |
14 | /**
15 | * Only check if a given filename is valid according to the OS rules.
16 | *
17 | * You still need to handle other failures when actually creating
18 | * the file (e.g. insufficient permissions, lack of drive space, security restrictions).
19 | * @param file the name of a file
20 | * @return true if the file is valid, false otherwise
21 | */
22 | public static boolean isFilenameValid(String file) {
23 | File f = new File(file);
24 | try {
25 | f.getCanonicalPath();
26 | return true;
27 | } catch (IOException e) {
28 | return false;
29 | }
30 | }
31 |
32 | public static void deleteDirectory(File dir) {
33 | if (!dir.exists()) return;
34 | File[] subs = dir.listFiles();
35 | if (subs != null) {
36 | for (File f : dir.listFiles()) {
37 | if (f.isFile()) {
38 | if(!f.delete()) {
39 | throw new IllegalStateException("delete file failed: "+f);
40 | }
41 | } else {
42 | deleteDirectory(f);
43 | }
44 | }
45 | }
46 | if(!dir.delete()) {
47 | throw new IllegalStateException("delete directory failed: "+dir);
48 | }
49 | }
50 |
51 | public static void deleteFile(File file) {
52 | if (!file.exists() || !file.isFile()) {
53 | return;
54 | }
55 | if (!file.delete()) {
56 | throw new IllegalStateException("delete file failed: "+file);
57 | }
58 | }
59 |
60 | /**
61 | * Copy a directory and all of its contents.
62 | *
63 | * @param from source file
64 | * @param to target file
65 | * @return success or failure
66 | */
67 | public static boolean copyDirectory(File from, File to) {
68 | return copyDirectory(from, to, (byte[]) null);
69 | }
70 |
71 | public static boolean copyDirectory(String from, String to) {
72 | return copyDirectory(new File(from), new File(to));
73 | }
74 |
75 | public static boolean copyDirectory(File from, File to, byte[] buffer) {
76 | if (from == null) return false;
77 | if (!from.exists()) return true;
78 | if (!from.isDirectory()) return false;
79 | if (to.exists()) return false;
80 | if (!to.mkdirs()) return false;
81 |
82 | String[] list = from.list();
83 | // Some JVMs return null for File.list() when the directory is empty.
84 | if (list != null) {
85 | if (buffer == null) buffer = new byte[BUFFER_SIZE]; // return this buffer to copy files
86 |
87 | for(int i = 0; i < list.length; i++) {
88 | String fileName = list[i];
89 |
90 | File entry = new File(from, fileName);
91 |
92 | if (entry.isDirectory()) {
93 | if (!copyDirectory(entry, new File(to, fileName), buffer)) {
94 | return false;
95 | }
96 | }
97 | else {
98 | if (!copyFile(entry, new File(to, fileName), buffer)) {
99 | return false;
100 | }
101 | }
102 | }
103 | }
104 |
105 | return true;
106 | }
107 |
108 | public static boolean copyFile(File from, File to, byte[] buf) {
109 | if (buf == null) buf = new byte[BUFFER_SIZE];
110 |
111 | FileInputStream from_s = null;
112 | FileOutputStream to_s = null;
113 |
114 | try {
115 | from_s = new FileInputStream(from);
116 | to_s = new FileOutputStream(to);
117 |
118 | for(int bytesRead = from_s.read(buf); bytesRead > 0; bytesRead = from_s.read(buf)) {
119 | to_s.write(buf, 0, bytesRead);
120 | }
121 |
122 | to_s.getFD().sync();
123 |
124 | } catch (IOException ioe) {
125 | return false;
126 | } finally {
127 | if (from_s != null) {
128 | try {
129 | from_s.close();
130 | from_s = null;
131 | } catch (IOException ioe) {
132 |
133 | }
134 | }
135 | if (to_s != null) {
136 | try {
137 | to_s.close();
138 | to_s = null;
139 | } catch (IOException ioe) {
140 | }
141 | }
142 | }
143 |
144 | return true;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/lrucache/v1/LruCacheV1.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v1;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * LruCache实现,V1版本,线程不安全
8 | *
9 | * Created on Jul, 2020 by @author bobo
10 | */
11 | public class LruCacheV1 {
12 |
13 | private int maxCapacity;
14 | private Map> map;
15 | private Node head, tail;
16 |
17 | private static class Node {
18 | private V value;
19 | private K key;
20 | private Node next, prev;
21 |
22 | public Node(K key, V value) {
23 | this.key = key;
24 | this.value = value;
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return value.toString();
30 | }
31 | }
32 |
33 | // 从双向链表中移除一个节点
34 | private void removeNode(Node node) {
35 | if (node == null) return;
36 |
37 | if (node.prev != null) {
38 | node.prev.next = node.next;
39 | } else {
40 | head = node.next;
41 | }
42 |
43 | if (node.next != null) {
44 | node.next.prev = node.prev;
45 | } else {
46 | tail = node.prev;
47 | }
48 | }
49 |
50 | // 向双向链表的尾部添加一个节点
51 | private void offerNode(Node node) {
52 | if (node == null) return;
53 |
54 | if (head == null) {
55 | head = tail = node;
56 | } else {
57 | tail.next = node;
58 | node.prev = tail;
59 | node.next = null;
60 | tail = node;
61 | }
62 | }
63 |
64 | public LruCacheV1(final int maxCapacity) {
65 | this.maxCapacity = maxCapacity;
66 | map = new HashMap<>();
67 | }
68 |
69 | public void put(K key, V value) {
70 | if (map.containsKey(key)) {
71 | Node node = map.get(key);
72 | node.value = value;
73 | removeNode(node);
74 | offerNode(node);
75 | } else {
76 | if (map.size() == maxCapacity) {
77 | map.remove(head.key);
78 | removeNode(head);
79 | }
80 | Node node = new Node<>(key, value);
81 | offerNode(node);
82 | map.put(key, node);
83 | }
84 | }
85 |
86 | public V get(K key) {
87 | Node node = map.get(key);
88 | if (node == null) return null;
89 | removeNode(node);
90 | offerNode(node);
91 | return node.value;
92 | }
93 |
94 | public int size() {
95 | return map.size();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/lrucache/v2/LruCacheV2.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v2;
2 |
3 |
4 | import java.util.HashMap;
5 | import java.util.Map;
6 | import java.util.concurrent.locks.Lock;
7 | import java.util.concurrent.locks.ReadWriteLock;
8 | import java.util.concurrent.locks.ReentrantReadWriteLock;
9 |
10 | /**
11 | * LruCache实现,V1版本,线程安全
12 | *
13 | * Created on Jul, 2020 by @author bobo
14 | */
15 | public class LruCacheV2 {
16 | private int maxCapacity;
17 | private Map> map;
18 | private Node head, tail;
19 |
20 | private ReadWriteLock lock = new ReentrantReadWriteLock();
21 | private Lock writeLock = lock.writeLock();
22 | private Lock readLock = lock.readLock();
23 |
24 | private static class Node {
25 | private V value;
26 | private K key;
27 | private Node next, prev;
28 |
29 | public Node(K key, V value) {
30 | this.key = key;
31 | this.value = value;
32 | }
33 |
34 | @Override
35 | public String toString() {
36 | return value.toString();
37 | }
38 | }
39 |
40 | // 从双向链表中移除一个节点
41 | private void removeNode(Node node) {
42 | if (node == null) return;
43 |
44 | if (node.prev != null) {
45 | node.prev.next = node.next;
46 | } else {
47 | head = node.next;
48 | }
49 |
50 | if (node.next != null) {
51 | node.next.prev = node.prev;
52 | } else {
53 | tail = node.prev;
54 | }
55 | }
56 |
57 | // 向双向链表的尾部添加一个节点
58 | private void offerNode(Node node) {
59 | if (node == null) return;
60 |
61 | if (head == null) {
62 | head = tail = node;
63 | } else {
64 | tail.next = node;
65 | node.prev = tail;
66 | node.next = null;
67 | tail = node;
68 | }
69 | }
70 |
71 | public LruCacheV2(final int maxCapacity) {
72 | this.maxCapacity = maxCapacity;
73 | map = new HashMap<>();
74 | }
75 |
76 | public void put(K key, V value) {
77 | writeLock.lock();
78 | try {
79 | if (map.containsKey(key)) {
80 | Node node = map.get(key);
81 | node.value = value;
82 | removeNode(node);
83 | offerNode(node);
84 | } else {
85 | if (map.size() >= maxCapacity) {
86 | map.remove(head.key);
87 | removeNode(head);
88 | }
89 | Node node = new Node<>(key, value);
90 | offerNode(node);
91 | map.put(key, node);
92 | }
93 | } finally {
94 | writeLock.unlock();
95 | }
96 | }
97 |
98 | public V get(K key) {
99 | writeLock.lock();
100 | try {
101 | Node node = map.get(key);
102 | if (node == null) return null;
103 | removeNode(node);
104 | offerNode(node);
105 | return node.value;
106 | } finally {
107 | writeLock.unlock();
108 | }
109 | }
110 |
111 | public int size() {
112 | readLock.lock();
113 | try {
114 | return map.size();
115 | } finally {
116 | readLock.unlock();
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/lrucache/v3/LruCacheV3.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v3;
2 |
3 | import com.spring2go.lrucache.v2.LruCacheV2;
4 |
5 | /**
6 | * LruCache实现,V3版本,线程安全+高并发
7 | *
8 | * Created on Jul, 2020 by @author bobo
9 | */
10 | public class LruCacheV3 {
11 |
12 | private LruCacheV2[] cacheSegments;
13 |
14 | public LruCacheV3(final int maxCapacity) {
15 | int cores = Runtime.getRuntime().availableProcessors();
16 | int concurrency = cores < 2 ? 2 : cores;
17 | cacheSegments = new LruCacheV2[concurrency];
18 | int segmentCapacity = maxCapacity / concurrency;
19 | if (maxCapacity % concurrency == 1) segmentCapacity++;
20 | for (int index = 0; index < cacheSegments.length; index++) {
21 | cacheSegments[index] = new LruCacheV2<>(segmentCapacity);
22 | }
23 | }
24 |
25 | public LruCacheV3(final int concurrency, final int maxCapacity) {
26 | cacheSegments = new LruCacheV2[concurrency];
27 | int segmentCapacity = maxCapacity / concurrency;
28 | if (maxCapacity % concurrency == 1) segmentCapacity++;
29 | for (int index = 0; index < cacheSegments.length; index++) {
30 | cacheSegments[index] = new LruCacheV2<>(segmentCapacity);
31 | }
32 | }
33 |
34 | private int segmentIndex(K key) {
35 | int hashCode = Math.abs(key.hashCode() * 31);
36 | return hashCode % cacheSegments.length;
37 | }
38 |
39 | private LruCacheV2 cache(K key) {
40 | return cacheSegments[segmentIndex(key)];
41 | }
42 |
43 | public void put(K key, V value) {
44 | cache(key).put(key, value);
45 | }
46 |
47 | public V get(K key) {
48 | return cache(key).get(key);
49 | }
50 |
51 | public int size() {
52 | int size = 0;
53 | for (LruCacheV2 cache : cacheSegments) {
54 | size += cache.size();
55 | }
56 | return size;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/AbstractDeque.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.util.Collection;
4 | import java.util.Deque;
5 | import java.util.Iterator;
6 | import java.util.NoSuchElementException;
7 |
8 | /**
9 | * Created on Jul, 2020 by @author bobo
10 | */
11 | public abstract class AbstractDeque implements Deque {
12 | protected AbstractDeque() {
13 | }
14 |
15 | @Override
16 | public void addFirst(E e) {
17 | if (!offerFirst(e))
18 | throw new IllegalStateException("Queue full");
19 |
20 | }
21 |
22 | @Override
23 | public void addLast(E e) {
24 | if (!offerLast(e))
25 | throw new IllegalStateException("Queue full");
26 | }
27 |
28 | @Override
29 | public E removeFirst() {
30 | E x = pollFirst();
31 | if (x != null)
32 | return x;
33 | else
34 | throw new NoSuchElementException();
35 | }
36 |
37 | @Override
38 | public E removeLast() {
39 | E x = pollLast();
40 | if (x != null)
41 | return x;
42 | else
43 | throw new NoSuchElementException();
44 | }
45 |
46 | @Override
47 | public E getFirst() {
48 | E x = peekFirst();
49 | if (x != null)
50 | return x;
51 | else
52 | throw new NoSuchElementException();
53 | }
54 |
55 | @Override
56 | public E getLast() {
57 | E x = peekLast();
58 | if (x != null)
59 | return x;
60 | else
61 | throw new NoSuchElementException();
62 | }
63 |
64 | @Override
65 | public boolean removeFirstOccurrence(Object o) {
66 | throw new UnsupportedOperationException();
67 | }
68 |
69 | @Override
70 | public boolean removeLastOccurrence(Object o) {
71 | throw new UnsupportedOperationException();
72 | }
73 |
74 | @Override
75 | public boolean add(E e) {
76 | if (offer(e))
77 | return true;
78 | else
79 | throw new IllegalStateException("Queue full");
80 | }
81 |
82 | @Override
83 | public boolean offer(E e) {
84 | return offerLast(e);
85 | }
86 |
87 | @Override
88 | public E remove() {
89 | E x = poll();
90 | if (x != null)
91 | return x;
92 | else
93 | throw new NoSuchElementException();
94 | }
95 |
96 | @Override
97 | public E poll() {
98 | return pollFirst();
99 | }
100 |
101 | @Override
102 | public E element() {
103 | E x = peek();
104 | if (x != null)
105 | return x;
106 | else
107 | throw new NoSuchElementException();
108 | }
109 |
110 | @Override
111 | public E peek() {
112 | return peekFirst();
113 | }
114 |
115 | @Override
116 | public void push(E e) {
117 | addFirst(e);
118 | }
119 |
120 | @Override
121 | public E pop() {
122 | return removeFirst();
123 | }
124 |
125 | @Override
126 | public boolean remove(Object o) {
127 | return removeFirstOccurrence(o);
128 | }
129 |
130 | @Override
131 | public boolean containsAll(Collection> c) {
132 | throw new UnsupportedOperationException();
133 | }
134 |
135 | @Override
136 | public boolean addAll(Collection extends E> c) {
137 | throw new UnsupportedOperationException();
138 | }
139 |
140 | @Override
141 | public boolean removeAll(Collection> c) {
142 | throw new UnsupportedOperationException();
143 | }
144 |
145 | @Override
146 | public boolean retainAll(Collection> c) {
147 | throw new UnsupportedOperationException();
148 | }
149 |
150 | @Override
151 | public void clear() {
152 | while (poll() != null)
153 | ;
154 | }
155 |
156 | @Override
157 | public boolean contains(Object o) {
158 | throw new UnsupportedOperationException();
159 | }
160 |
161 | @Override
162 | public int size() {
163 | throw new UnsupportedOperationException();
164 | }
165 |
166 | @Override
167 | public boolean isEmpty() {
168 | throw new UnsupportedOperationException();
169 | }
170 |
171 | @Override
172 | public Iterator iterator() {
173 | throw new UnsupportedOperationException();
174 | }
175 |
176 | @Override
177 | public Object[] toArray() {
178 | throw new UnsupportedOperationException();
179 | }
180 |
181 | @Override
182 | public T[] toArray(T[] a) {
183 | throw new UnsupportedOperationException();
184 | }
185 |
186 | @Override
187 | public Iterator descendingIterator() {
188 | throw new UnsupportedOperationException();
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/BehindStore.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public interface BehindStore extends CacheStore {
9 |
10 | void setSyncInterval(long syncInterval);
11 | Stats prevStats();
12 | void prevStats(Stats stats);
13 | public static class Stats {
14 | long updateCount;
15 | long updateCost;
16 |
17 | long removedCount;
18 | long removedCost;
19 |
20 | long purgedCount;
21 | long purgedCost;
22 |
23 | public Stats(long updateCount, long updateCost, long removedCount, long removedCost, long purgedCount, long purgedCost) {
24 | this.updateCount = updateCount;
25 | this.updateCost = updateCost;
26 | this.removedCount = removedCount;
27 | this.removedCost = removedCost;
28 | this.purgedCount = purgedCount;
29 | this.purgedCost = purgedCost;
30 | }
31 |
32 | public Stats plus(Stats other) {
33 | return new Stats(
34 | Math.max(0, updateCount + other.getUpdateCount()),
35 | Math.max(0, updateCost + other.getUpdateCost()),
36 | Math.max(0, removedCount + other.getRemovedCount()),
37 | Math.max(0, removedCost + other.getRemovedCost()),
38 | Math.max(0, purgedCount + other.getPurgedCount()),
39 | Math.max(0, purgedCost + other.getPurgedCost())
40 | );
41 | }
42 |
43 | public Stats minus(Stats other) {
44 | return new Stats(
45 | Math.max(0, updateCount - other.getUpdateCount()),
46 | Math.max(0, updateCost - other.getUpdateCost()),
47 | Math.max(0, removedCount - other.getRemovedCount()),
48 | Math.max(0, removedCost - other.getRemovedCost()),
49 | Math.max(0, purgedCount - other.getPurgedCount()),
50 | Math.max(0, purgedCost - other.getPurgedCost())
51 | );
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return "Stats{" +
57 | "updateCount=" + updateCount +
58 | ", updateCost=" + updateCost +
59 | ", removedCount=" + removedCount +
60 | ", removedCost=" + removedCost +
61 | ", purgedCount=" + purgedCount +
62 | ", purgedCost=" + purgedCost +
63 | '}';
64 | }
65 |
66 | public long getUpdateCount() {
67 | return updateCount;
68 | }
69 |
70 | public long getUpdateCost() {
71 | return updateCost;
72 | }
73 |
74 | public long getRemovedCount() {
75 | return removedCount;
76 | }
77 |
78 | public long getRemovedCost() {
79 | return removedCost;
80 | }
81 |
82 | public long getPurgedCount() {
83 | return purgedCount;
84 | }
85 |
86 | public long getPurgedCost() {
87 | return purgedCost;
88 | }
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/Cache.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 | import java.util.Map;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public interface Cache {
10 | V get(K key);
11 |
12 | V get(K key, AccessLevel level);
13 |
14 | V get(K key, UpdateTimestamp strategy);
15 |
16 | V get(K key, AccessLevel level, UpdateTimestamp strategy);
17 |
18 | V put(K key, V value);
19 |
20 | V put(K key, V value, ExpirationPolicy expirationPolicy);
21 |
22 | V putIfAbsent(K key, V value);
23 |
24 | V putIfAbsent(K key, V value, ExpirationPolicy expirationPolicy);
25 |
26 | void putAll(Map extends K, ? extends V> map);
27 |
28 | void putAll(Map extends K, ? extends V> map, ExpirationPolicy expirationPolicy);
29 |
30 | V replace(K key, V value);
31 |
32 | boolean replace(K key, V oldValue, V newValue);
33 |
34 | V remove(K key);
35 |
36 | boolean remove(K key, V value);
37 |
38 | boolean contains(K key);
39 |
40 | CacheStats stats();
41 |
42 | void close();
43 |
44 | enum AccessLevel{
45 | MEMORY(0),
46 | EVICT_STORE(1),
47 | BEHIND_STORE(2);
48 |
49 | private final int code;
50 | AccessLevel(int code) {
51 | this.code = code;
52 | }
53 |
54 | public int getCode() {
55 | return code;
56 | }
57 |
58 | public boolean accessEvictStore(){
59 | return this.code >= EVICT_STORE.getCode();
60 | }
61 |
62 | public boolean accessBehindStore(){
63 | return this.code >= BEHIND_STORE.getCode();
64 | }
65 | }
66 |
67 | enum UpdateTimestamp {
68 | Access(0),
69 | Write(1),
70 | Create(2);
71 |
72 | private final int code;
73 | UpdateTimestamp(int code) {
74 | this.code = code;
75 | }
76 |
77 | public int getCode() {
78 | return code;
79 | }
80 |
81 | public boolean updateAccessTime(){
82 | return this.code >= Access.getCode();
83 | }
84 |
85 | public boolean updateWriteTime(){
86 | return this.code >= Write.getCode();
87 | }
88 |
89 | public boolean updateCreateTime(){
90 | return this.code >= Create.getCode();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheBuilder.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | import com.spring2go.okcache.impl.CacheImpl;
9 |
10 | /**
11 | * Created on Jul, 2020 by @author bobo
12 | */
13 | public class CacheBuilder {
14 | private int initCapacity = 1;
15 | private long maximumSize = 2000;
16 | private int concurrencyLevel = 1;
17 |
18 | private long expireAfterAccess = -1l;
19 | private long expireAfterWrite = -1l;
20 | private long expireAfterCreate = -1l;
21 |
22 | private float evictionStartFactor = 0.85f;
23 | private float evictionStopFactor = 0.70f;
24 |
25 | private final List> evictStores = new ArrayList>();
26 | private final List> writeBehindStores = new ArrayList>();
27 |
28 | public CacheBuilder initialCapacity(int initialCapacity) {
29 | checkArgument(initialCapacity >= 0);
30 | this.initCapacity = initialCapacity;
31 | return this;
32 | }
33 |
34 | public CacheBuilder maximumSize(long size) {
35 | checkArgument(size >= 0, "maximum size must not be negative");
36 | this.maximumSize = size;
37 | return this;
38 | }
39 |
40 | public CacheBuilder concurrencyLevel(int concurrencyLevel) {
41 | checkArgument(concurrencyLevel > 0);
42 | this.concurrencyLevel = concurrencyLevel;
43 | return this;
44 | }
45 |
46 | public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) {
47 | checkArgument(duration >= 0, "duration cannot be negative:" + duration + unit);
48 | this.expireAfterAccess = unit.toMillis(duration);
49 | return this;
50 | }
51 |
52 | public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) {
53 | checkArgument(duration >= 0, "duration cannot be negative:" + duration + unit);
54 | this.expireAfterWrite = unit.toMillis(duration);
55 | return this;
56 | }
57 |
58 | public CacheBuilder expireAfterCreate(long duration, TimeUnit unit) {
59 | checkArgument(duration >= 0, "duration cannot be negative:" + duration + unit);
60 | this.expireAfterCreate = unit.toMillis(duration);
61 | return this;
62 | }
63 |
64 | public CacheBuilder evictionFactors(float startFactor, float stopFactor) {
65 | checkArgument(startFactor > 0 && startFactor < 1 && stopFactor > 0 && stopFactor < 1, "eviction factors must be between 0 an 1");
66 | checkArgument(startFactor > stopFactor, "startFactor must be great than stopFactor");
67 | this.evictionStartFactor = startFactor;
68 | this.evictionStopFactor = stopFactor;
69 | return this;
70 | }
71 |
72 | public CacheBuilder addEvictStore(CacheStore store) {
73 | checkArgument(store!=null, "evictStore must not be null.");
74 | checkArgument(!evictStores.contains(store), "the evictStore has already added");
75 |
76 | evictStores.add(store);
77 | return this;
78 | }
79 |
80 | public CacheBuilder addWriteBehindStore(BehindStore store) {
81 | checkArgument(store!=null, "writeBehindStore must not be null.");
82 | checkArgument(!writeBehindStores.contains(store), "the writeBehindStore has already added");
83 |
84 | writeBehindStores.add(store);
85 | return this;
86 | }
87 |
88 | public Cache build() {
89 | return new CacheImpl(initCapacity, maximumSize, concurrencyLevel, expireAfterAccess, expireAfterWrite, expireAfterCreate, evictionStartFactor, evictionStopFactor, evictStores, writeBehindStores);
90 | }
91 |
92 | public static CacheBuilder extends Serializable, ? extends Serializable> newBuilder(){
93 | return new CacheBuilder();
94 | }
95 |
96 | public static CacheBuilder newBuilder(Class key, Class value){
97 | return new CacheBuilder();
98 | }
99 |
100 |
101 | public static void checkArgument(boolean expression) {
102 | if (!expression) {
103 | throw new IllegalArgumentException();
104 | }
105 | }
106 |
107 | public static void checkArgument(boolean expression, Object errorMessage) {
108 | if (!expression) {
109 | throw new IllegalArgumentException(String.valueOf(errorMessage));
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheEntry.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public interface CacheEntry extends Serializable {
9 | K getKey();
10 |
11 | V getValue();
12 |
13 | void setValue(V value);
14 |
15 | /**
16 | * Returns the time that this entry was last accessed, in ms.
17 | */
18 | long getAccessTime();
19 |
20 | /**
21 | * Sets the entry access time in ns.
22 | */
23 | void setAccessTime(long time);
24 |
25 | /**
26 | * Returns the time that this entry was last written, in ms.
27 | */
28 | long getWriteTime();
29 |
30 | /**
31 | * Sets the entry write time in ms.
32 | */
33 | void setWriteTime(long time);
34 |
35 | /**
36 | * Returns the time that this entry was created, in ms.
37 | */
38 | long getCreateTime();
39 |
40 | /**
41 | * Sets the entry created time in ms.
42 | */
43 | void setCreateTime(long time);
44 |
45 | long getExpireAfterAccess();
46 | void setExpireAfterAccess(long time);
47 | long getExpireAfterWrite();
48 | void setExpireAfterWrite(long time);
49 | long getExpireAfterCreate();
50 | void setExpireAfterCreate(long time);
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheEntryHelper.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public class CacheEntryHelper {
9 | public static boolean isExpired(CacheEntry entry, long msTime){
10 | long t;
11 | if((t = entry.getExpireAfterAccess()) > 0 && (msTime - entry.getAccessTime() > t)) return true;
12 | if((t = entry.getExpireAfterWrite()) > 0 && (msTime - entry.getWriteTime() > t)) return true;
13 | if((t = entry.getExpireAfterCreate()) > 0 && (msTime - entry.getCreateTime() > t)) return true;
14 | return false;
15 | }
16 |
17 | public static long calculateTTL(CacheEntry entry, long msTime) {
18 | long ttl = Long.MAX_VALUE, t;
19 | if((t = entry.getExpireAfterAccess()) > 0) ttl = entry.getAccessTime() + t - msTime;
20 | if((t = entry.getExpireAfterWrite()) > 0) ttl = Math.min(ttl,entry.getWriteTime() + t - msTime);
21 | if((t = entry.getExpireAfterCreate()) > 0) ttl = Math.min(ttl,entry.getCreateTime() + t - msTime);
22 | return ttl;
23 | }
24 |
25 | public static long calculateExpiredTimeInMS(CacheEntry entry) {
26 | long now = TimeHelper.nowMs();
27 | long ttl = calculateTTL(entry, now);
28 | if(ttl == Long.MAX_VALUE) return ttl;
29 | return now + ttl;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheStats.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | /**
4 | * Created on Jul, 2020 by @author bobo
5 | */
6 | public class CacheStats {
7 | private final long hitCount;
8 | private final long missCount;
9 |
10 | private final long memoryHitCount;
11 | private final long memoryMissCount;
12 |
13 | private final long createCount;
14 | private final long updateCount;
15 | private final long removeCount;
16 |
17 | private final long evictCount;
18 | private final long expireCount;
19 |
20 | private final long evictStoreHitCount;
21 | private final long evictStoreMissCount;
22 |
23 | private final long behindStoreHitCount;
24 | private final long behindStoreMissCount;
25 |
26 | private final long size;
27 | private final long memorySize;
28 |
29 | public CacheStats(long hitCount, long missCount, long memoryHitCount, long memoryMissCount, long createCount, long updateCount, long removeCount, long evictCount, long expireCount, long evictStoreHitCount, long evictStoreMissCount, long behindStoreHitCount, long behindStoreMissCount, long size, long memorySize) {
30 | this.hitCount = hitCount;
31 | this.missCount = missCount;
32 | this.memoryHitCount = memoryHitCount;
33 | this.memoryMissCount = memoryMissCount;
34 | this.createCount = createCount;
35 | this.updateCount = updateCount;
36 | this.removeCount = removeCount;
37 | this.evictCount = evictCount;
38 | this.expireCount = expireCount;
39 | this.evictStoreHitCount = evictStoreHitCount;
40 | this.evictStoreMissCount = evictStoreMissCount;
41 | this.behindStoreHitCount = behindStoreHitCount;
42 | this.behindStoreMissCount = behindStoreMissCount;
43 | this.size = size;
44 | this.memorySize = memorySize;
45 | }
46 |
47 | public CacheStats() {
48 | this(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
49 | }
50 |
51 | public long requestCount() {
52 | return hitCount + missCount;
53 | }
54 |
55 | public double hitRate() {
56 | double r = requestCount();
57 | return r > 0 ? hitCount / r : 1.0;
58 | }
59 |
60 | public double memoryHitRate() {
61 | double r = memoryHitCount + memoryMissCount;
62 | return r > 0 ? memoryHitCount / r : 1.0;
63 | }
64 |
65 | public double evictStoreHitRate() {
66 | double r = evictStoreHitCount + evictStoreMissCount;
67 | return r > 0 ? evictStoreHitCount / r : 1.0;
68 | }
69 |
70 | public double behindStoreHitRate() {
71 | double r = behindStoreHitCount + behindStoreMissCount;
72 | return r > 0 ? behindStoreHitCount / r : 1.0;
73 | }
74 |
75 | public long getHitCount() {
76 | return hitCount;
77 | }
78 |
79 | public long getMissCount() {
80 | return missCount;
81 | }
82 |
83 | public long getMemoryHitCount() {
84 | return memoryHitCount;
85 | }
86 |
87 | public long getMemoryMissCount() {
88 | return memoryMissCount;
89 | }
90 |
91 | public long getCreateCount() {
92 | return createCount;
93 | }
94 |
95 | public long getUpdateCount() {
96 | return updateCount;
97 | }
98 |
99 | public long getRemoveCount() {
100 | return removeCount;
101 | }
102 |
103 | public long getEvictCount() {
104 | return evictCount;
105 | }
106 |
107 | public long getExpireCount() {
108 | return expireCount;
109 | }
110 |
111 | public long getEvictStoreHitCount() {
112 | return evictStoreHitCount;
113 | }
114 |
115 | public long getEvictStoreMissCount() {
116 | return evictStoreMissCount;
117 | }
118 |
119 | public long getBehindStoreHitCount() {
120 | return behindStoreHitCount;
121 | }
122 |
123 | public long getBehindStoreMissCount() {
124 | return behindStoreMissCount;
125 | }
126 |
127 | public long getSize() {
128 | return size;
129 | }
130 |
131 | public long getMemorySize() {
132 | return memorySize;
133 | }
134 |
135 | public CacheStats minus(CacheStats other) {
136 | return new CacheStats(
137 | Math.max(0, hitCount - other.hitCount),
138 | Math.max(0, missCount - other.missCount),
139 | Math.max(0, memoryHitCount - other.memoryHitCount),
140 | Math.max(0, memoryMissCount - other.memoryMissCount),
141 | Math.max(0, createCount - other.createCount),
142 | Math.max(0, updateCount - other.updateCount),
143 | Math.max(0, removeCount - other.removeCount),
144 | Math.max(0, evictCount - other.evictCount),
145 | Math.max(0, expireCount - other.expireCount),
146 | Math.max(0, evictStoreHitCount - other.evictStoreHitCount),
147 | Math.max(0, evictStoreMissCount - other.evictStoreMissCount),
148 | Math.max(0, behindStoreHitCount - other.behindStoreHitCount),
149 | Math.max(0, behindStoreMissCount - other.behindStoreMissCount),
150 | Math.max(0, size - other.size),
151 | Math.max(0, memorySize - other.memorySize)
152 | );
153 | }
154 |
155 | public CacheStats plus(CacheStats other) {
156 | return new CacheStats(
157 | Math.max(0, hitCount + other.hitCount),
158 | Math.max(0, missCount + other.missCount),
159 | Math.max(0, memoryHitCount + other.memoryHitCount),
160 | Math.max(0, memoryMissCount + other.memoryMissCount),
161 | Math.max(0, createCount + other.createCount),
162 | Math.max(0, updateCount + other.updateCount),
163 | Math.max(0, removeCount + other.removeCount),
164 | Math.max(0, evictCount + other.evictCount),
165 | Math.max(0, expireCount + other.expireCount),
166 | Math.max(0, evictStoreHitCount + other.evictStoreHitCount),
167 | Math.max(0, evictStoreMissCount + other.evictStoreMissCount),
168 | Math.max(0, behindStoreHitCount + other.behindStoreHitCount),
169 | Math.max(0, behindStoreMissCount + other.behindStoreMissCount),
170 | Math.max(0, size + other.size),
171 | Math.max(0, memorySize + other.memorySize)
172 | );
173 | }
174 |
175 | @Override
176 | public String toString() {
177 | return new StringBuilder(this.getClass().getSimpleName()).append(":\n")
178 | .append("\t").append("hitCount").append(":").append(hitCount).append("\n")
179 | .append("\t").append("missCount").append(":").append(missCount).append("\n")
180 | .append("\t").append("memoryHitCount").append(":").append(memoryHitCount).append("\n")
181 | .append("\t").append("memoryMissCount").append(":").append(memoryMissCount).append("\n")
182 | .append("\t").append("createCount").append(":").append(createCount).append("\n")
183 | .append("\t").append("updateCount").append(":").append(updateCount).append("\n")
184 | .append("\t").append("removeCount").append(":").append(removeCount).append("\n")
185 | .append("\t").append("evictCount").append(":").append(evictCount).append("\n")
186 | .append("\t").append("expireCount").append(":").append(expireCount).append("\n")
187 | .append("\t").append("evictStoreHitCount").append(":").append(evictStoreHitCount).append("\n")
188 | .append("\t").append("evictStoreMissCount").append(":").append(evictStoreMissCount).append("\n")
189 | .append("\t").append("behindStoreHitCount").append(":").append(behindStoreHitCount).append("\n")
190 | .append("\t").append("behindStoreMissCount").append(":").append(behindStoreMissCount).append("\n")
191 | .append("\t").append("size").append(":").append(size).append("\n")
192 | .append("\t").append("memorySize").append(":").append(memorySize).append("\n")
193 | .toString();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheStatsCounter.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.util.concurrent.atomic.AtomicLong;
4 |
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class CacheStatsCounter {
10 | final AtomicLong memoryHitCount = new AtomicLong(0);
11 | final AtomicLong memoryMissCount = new AtomicLong(0);
12 |
13 | final AtomicLong createCount = new AtomicLong(0);
14 | final AtomicLong updateCount = new AtomicLong(0);
15 | final AtomicLong removeCount = new AtomicLong(0);
16 |
17 | final AtomicLong evictCount = new AtomicLong(0);
18 | final AtomicLong expireCount = new AtomicLong(0);
19 |
20 | final AtomicLong evictStoreHitCount = new AtomicLong(0);
21 | final AtomicLong evictStoreMissCount = new AtomicLong(0);
22 |
23 | final AtomicLong behindStoreHitCount = new AtomicLong(0);
24 | final AtomicLong behindStoreMissCount = new AtomicLong(0);
25 |
26 | final AtomicLong size = new AtomicLong(0);
27 | final AtomicLong memorySize = new AtomicLong(0);
28 |
29 | public void memoryHits(int count) {
30 | memoryHitCount.addAndGet(count);
31 | }
32 |
33 | public void memoryMisses(int count) {
34 | memoryMissCount.addAndGet(count);
35 | }
36 |
37 | public void recordCreates(int count) {
38 | createCount.addAndGet(count);
39 | }
40 |
41 | public void recordUpdates(int count) {
42 | updateCount.addAndGet(count);
43 | }
44 |
45 | public void recordRemoves(int count) {
46 | removeCount.addAndGet(count);
47 | }
48 |
49 | public void recordEvicts(int count) {
50 | evictCount.addAndGet(count);
51 | }
52 |
53 | public void recordExpires(int count) {
54 | expireCount.addAndGet(count);
55 | }
56 |
57 | public void evictStoreHits(int count) {
58 | evictStoreHitCount.addAndGet(count);
59 | }
60 |
61 | public void evictStoreMisses(int count) {
62 | evictStoreMissCount.addAndGet(count);
63 | }
64 |
65 | public void behindStoreHits(int count) {
66 | behindStoreHitCount.addAndGet(count);
67 | }
68 |
69 | public void behindStoreMisses(int count) {
70 | behindStoreMissCount.addAndGet(count);
71 | }
72 |
73 | public void sizeIncrement() {
74 | size.incrementAndGet();
75 | }
76 |
77 | public void sizeDecrement() {
78 | size.decrementAndGet();
79 | }
80 |
81 | public void memorySizeIncrement() {
82 | memorySize.incrementAndGet();
83 | }
84 |
85 | public void memorySizeDecrement() {
86 | memorySize.decrementAndGet();
87 | }
88 |
89 | public CacheStats snapshot() {
90 | return new CacheStats(
91 | memoryHitCount.get() + evictStoreHitCount.get() + behindStoreHitCount.get(),
92 | memoryMissCount.get() - evictStoreHitCount.get() - behindStoreHitCount.get(),
93 | memoryHitCount.get(),
94 | memoryMissCount.get(),
95 | createCount.get(),
96 | updateCount.get(),
97 | removeCount.get(),
98 | evictCount.get(),
99 | expireCount.get(),
100 | evictStoreHitCount.get(),
101 | evictStoreMissCount.get(),
102 | behindStoreHitCount.get(),
103 | behindStoreMissCount.get(),
104 | size.get(),
105 | memorySize.get()
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/CacheStore.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public interface CacheStore {
9 | void store(CacheEntry entry) throws Exception;
10 |
11 | CacheEntry load(K key) throws Exception;
12 |
13 | CacheEntry remove(K key) throws Exception;
14 |
15 | void close();
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/DummyCacheStore.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class DummyCacheStore implements CacheStore {
10 |
11 | @Override
12 | public void store(CacheEntry entry) {
13 | }
14 |
15 | @Override
16 | public CacheEntry load(K key) {
17 | return null;
18 | }
19 |
20 | @Override
21 | public CacheEntry remove(K key) {
22 | return null;
23 | }
24 |
25 | @Override
26 | public void close() {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/ExpirationPolicy.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | /**
4 | * Created on Jul, 2020 by @author bobo
5 | */
6 | public class ExpirationPolicy {
7 | public static ExpirationPolicy never() {
8 | return new ExpirationPolicy(-1l, -1l, -1l);
9 | }
10 |
11 | public static ExpirationPolicy afterAccess(long ms) {
12 | return new ExpirationPolicy(ms, -1l, -1l);
13 | }
14 |
15 | public static ExpirationPolicy afterWrite(long ms) {
16 | return new ExpirationPolicy(-1l, ms, -1l);
17 | }
18 |
19 | public static ExpirationPolicy afterCreate(long ms) {
20 | return new ExpirationPolicy(-1l, -1l, ms);
21 | }
22 |
23 | public static ExpirationPolicy afterAccessOrWrite(long access, long write) {
24 | return new ExpirationPolicy(access, write, -1l);
25 | }
26 |
27 | public static ExpirationPolicy after(long access, long write, long create) {
28 | return new ExpirationPolicy(access, write, create);
29 | }
30 |
31 | long afterAccess = -1l;
32 | long afterWrite = -1l;
33 | long afterCreate = -1l;
34 |
35 | private ExpirationPolicy(long afterAccess, long afterWrite, long afterCreate) {
36 | this.afterAccess = afterAccess;
37 | this.afterWrite = afterWrite;
38 | this.afterCreate = afterCreate;
39 | }
40 |
41 | public long getAfterAccess() {
42 | return afterAccess;
43 | }
44 |
45 | public long getAfterWrite() {
46 | return afterWrite;
47 | }
48 |
49 | public long getAfterCreate() {
50 | return afterCreate;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/ICache.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | *
9 | * The Interface ICache.
10 | *
11 | * @param the key type
12 | */
13 | public interface ICache extends Closeable {
14 |
15 | /**
16 | * Puts the value with the specified key.
17 | *
18 | * @param key the key
19 | * @param value the value
20 | * @throws IOException
21 | */
22 | void put(K key, byte[] value) throws IOException;
23 |
24 | /**
25 | * Puts the value with specified key and time to idle in milliseconds.
26 | *
27 | * @param key the key
28 | * @param value the value
29 | * @param ttl the time to idle value in milliseconds
30 | * @throws IOException
31 | */
32 | void put(K key, byte[] value, long tti) throws IOException;
33 |
34 | /**
35 | * Gets the value with the specified key.
36 | *
37 | * @param key the key
38 | * @return the value
39 | * @throws IOException
40 | */
41 | byte[] get(K key) throws IOException;
42 |
43 | /**
44 | * Delete the value with the specified key.
45 | *
46 | * @param key the key
47 | * @return the value
48 | * @throws IOException
49 | */
50 | byte[] delete(K key) throws IOException;
51 |
52 | /**
53 | * Check if Cache contains the specified key.
54 | *
55 | * @param key the key
56 | * @return true, if successful
57 | */
58 | boolean contains(K key);
59 |
60 | /**
61 | * Clear the cache.
62 | */
63 | void clear();
64 |
65 | /**
66 | * Calculates the Hit ratio.
67 | *
68 | * @return the double
69 | */
70 | double hitRatio();
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/JavaSerializer.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.*;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public class JavaSerializer implements Serializer {
9 | public byte[] serialize(Object o) throws IOException {
10 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
11 | ObjectOutputStream oos = new ObjectOutputStream(bos);
12 | oos.writeObject(o);
13 | oos.flush();
14 | return bos.toByteArray();
15 | }
16 |
17 | public Object deserialize(byte[] value) throws IOException, ClassNotFoundException {
18 | ByteArrayInputStream bis = new ByteArrayInputStream(value);
19 | ObjectInputStream ois = new ObjectInputStream(bis);
20 | return ois.readObject();
21 | }
22 |
23 | @Override
24 | public K deserialize(byte[] value, Class clazz) throws Exception {
25 | return (K)deserialize(value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/Serializer.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Be responsible for serializing an object to a byte array
7 | * or deserializing an object from a byte array.
8 | *
9 | * Created on Jul, 2020 by @author bobo
10 | */
11 | public interface Serializer {
12 |
13 | /**
14 | * Serialize an object to a byte array.
15 | *
16 | * @param o To be serialized.
17 | * @return the result of serializing.
18 | * @throws Exception
19 | */
20 | public byte[] serialize(Object o) throws Exception;
21 |
22 | /**
23 | * Deserialize an object from a byte array.
24 | * And be able to automatically detect the class type to be deserialized.
25 | *
26 | * @param value
27 | * @return
28 | * @throws Exception
29 | */
30 | public Object deserialize(byte[] value) throws Exception;
31 |
32 | /**
33 | * Deserialize an object from a byte array.
34 | *
35 | * @param value
36 | * @param clazz
37 | * @param
38 | * @return
39 | * @throws Exception
40 | */
41 | public K deserialize(byte[] value, Class clazz) throws Exception;
42 |
43 | }
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/TimeHelper.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | /**
4 | * Created on Jul, 2020 by @author bobo
5 | */
6 | public class TimeHelper {
7 | public static long nowMs(){
8 | return System.currentTimeMillis();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/impl/CacheImpl.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache.impl;
2 |
3 | import java.io.Serializable;
4 | import java.lang.reflect.Field;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import sun.misc.Unsafe;
10 |
11 | import com.spring2go.okcache.BehindStore;
12 | import com.spring2go.okcache.Cache;
13 | import com.spring2go.okcache.CacheStats;
14 | import com.spring2go.okcache.CacheStore;
15 | import com.spring2go.okcache.ExpirationPolicy;
16 |
17 | /**
18 | * Created on Jul, 2020 by @author bobo
19 | */
20 | public class CacheImpl implements Cache {
21 | private final List> evictStores = new ArrayList>();
22 | private final List> writeBehindStores = new ArrayList>();
23 |
24 | private int initCapacity = 2000;
25 | private long maximumSize = 2000;
26 | private int concurrencyLevel = 1;
27 |
28 | private long expireAfterAccess = -1l;
29 | private long expireAfterWrite = -1l;
30 | private long expireAfterCreate = -1l;
31 |
32 | private float evictionStartFactor = 0.85f;
33 | private float evictionStopFactor = 0.70f;
34 |
35 | private int segmentCount = 1;
36 | private int segmentShift;
37 | private int segmentMask = 0;
38 | private final CacheSegment[] segments;
39 |
40 | public CacheImpl(int initCapacity, long maximumSize, int concurrencyLevel, long expireAfterAccess, long expireAfterWrite, long expireAfterCreate, float evictionStartFactor, float evictionStopFactor, List> evictStores, List> writeBehindStores) {
41 | this.initCapacity = Math.min(initCapacity, (int) maximumSize);
42 | this.maximumSize = Math.max(initCapacity, maximumSize);
43 | this.concurrencyLevel = Math.max(concurrencyLevel, 1);
44 | this.expireAfterAccess = expireAfterAccess;
45 | this.expireAfterWrite = expireAfterWrite;
46 | this.expireAfterCreate = expireAfterCreate;
47 | this.evictionStartFactor = Math.max(evictionStartFactor, evictionStopFactor);
48 | this.evictionStopFactor = Math.min(evictionStopFactor, evictionStartFactor);
49 | this.evictStores.addAll(evictStores);
50 | this.writeBehindStores.addAll(writeBehindStores);
51 |
52 | int scount=1;
53 | int sshift=0;
54 | while (scount < this.concurrencyLevel) {
55 | ++sshift;
56 | scount<<=1;
57 | }
58 | this.segmentCount = scount;
59 | this.segmentShift = sshift;
60 |
61 | int init = this.initCapacity / scount + 1;
62 | long max = this.maximumSize / scount + 1;
63 |
64 | this.segmentMask = this.segmentCount - 1;
65 | segments = new CacheSegment[scount];
66 |
67 | CacheSegment s;
68 | for (int i = 0; i < scount; i++) {
69 | s = new CacheSegment(init, max, evictionStartFactor, evictionStopFactor, evictStores, writeBehindStores);
70 | UNSAFE.putOrderedObject(segments, SBASE + (i << SSHIFT), s);
71 | }
72 | }
73 |
74 | @Override
75 | public V get(K key) {
76 | return get(key, AccessLevel.EVICT_STORE, UpdateTimestamp.Access);
77 | }
78 |
79 | @Override
80 | public V get(K key, AccessLevel level) {
81 | return get(key, level, UpdateTimestamp.Access);
82 | }
83 |
84 | @Override
85 | public V get(K key, UpdateTimestamp strategy) {
86 | return get(key, AccessLevel.EVICT_STORE, strategy);
87 | }
88 |
89 | @Override
90 | public V get(K key, AccessLevel level, UpdateTimestamp strategy) {
91 | int hash = hash(key);
92 | CacheSegment s = segmentForHash(hash);
93 | return s.get(hash, key , level, strategy);
94 | }
95 |
96 | @Override
97 | public V put(K key, V value) {
98 | return put(key, value, ExpirationPolicy.after(expireAfterAccess, expireAfterWrite, expireAfterCreate));
99 | }
100 |
101 | @Override
102 | public V put(K key, V value, ExpirationPolicy expirationPolicy) {
103 | int hash = hash(key);
104 | if (value == null)
105 | throw new NullPointerException();
106 | CacheSegment s = segmentForHash(hash);
107 | return s.put(hash, key, value, expirationPolicy, false);
108 | }
109 |
110 | @Override
111 | public V putIfAbsent(K key, V value) {
112 | return putIfAbsent(key, value, ExpirationPolicy.after(expireAfterAccess, expireAfterWrite, expireAfterCreate));
113 | }
114 |
115 | @Override
116 | public V putIfAbsent(K key, V value, ExpirationPolicy expirationPolicy) {
117 | int hash = hash(key);
118 | if (value == null)
119 | throw new NullPointerException();
120 | CacheSegment s = segmentForHash(hash);
121 | return s.put(hash, key, value, expirationPolicy, true);
122 | }
123 |
124 | @Override
125 | public void putAll(Map extends K, ? extends V> map) {
126 | for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
127 | put(entry.getKey(), entry.getValue());
128 | }
129 | }
130 |
131 | @Override
132 | public void putAll(Map extends K, ? extends V> map, ExpirationPolicy expirationPolicy) {
133 | for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
134 | put(entry.getKey(), entry.getValue(), expirationPolicy);
135 | }
136 | }
137 |
138 | @Override
139 | public V replace(K key, V value) {
140 | int hash = hash(key);
141 | if (value == null)
142 | throw new NullPointerException();
143 | CacheSegment s = segmentForHash(hash);
144 | return s == null ? null : s.replace(key, hash, value);
145 | }
146 |
147 | @Override
148 | public boolean replace(K key, V oldValue, V newValue) {
149 | int hash = hash(key);
150 | if (oldValue == null || newValue == null)
151 | throw new NullPointerException();
152 | CacheSegment s = segmentForHash(hash);
153 | return s != null && s.replace(key, hash, oldValue, newValue);
154 | }
155 |
156 | @Override
157 | public V remove(K key) {
158 | int hash = hash(key);
159 | CacheSegment s = segmentForHash(hash);
160 | return s == null ? null : s.remove(key, hash, null);
161 | }
162 |
163 | @Override
164 | public boolean remove(K key, V value) {
165 | int hash = hash(key);
166 | CacheSegment s;
167 | return value != null && (s = segmentForHash(hash)) != null &&
168 | s.remove(key, hash, value) != null;
169 | }
170 |
171 | @Override
172 | public boolean contains(K key) {
173 | int hash = hash(key);
174 | if (key == null)
175 | return false;
176 | CacheSegment s = segmentForHash(hash);
177 | return s.contains(hash, key);
178 | }
179 |
180 | @Override
181 | public CacheStats stats() {
182 | CacheStats stats = new CacheStats();
183 | for (CacheSegment segament : segments) {
184 | stats = stats.plus(segament.stats());
185 | }
186 | return stats;
187 | }
188 |
189 | @Override
190 | public void close() {
191 | for(CacheStore cs : evictStores) {
192 | cs.close();
193 | }
194 | for(CacheStore cs : writeBehindStores) {
195 | cs.close();
196 | }
197 | }
198 |
199 | private int hash(Object k) {
200 | int h = 0;
201 | h ^= k.hashCode();
202 |
203 | // Spread bits to regularize both segment and index locations,
204 | // using variant of single-word Wang/Jenkins hash.
205 | h += (h << 15) ^ 0xffffcd7d;
206 | h ^= (h >>> 10);
207 | h += (h << 3);
208 | h ^= (h >>> 6);
209 | h += (h << 2) + (h << 14);
210 | return h ^ (h >>> 16);
211 | }
212 |
213 | @SuppressWarnings("unchecked")
214 | static final CacheSegment segmentAt(CacheSegment[] ss, int j) {
215 | long u = (j << SSHIFT) + SBASE;
216 | return ss == null ? null :
217 | (CacheSegment) UNSAFE.getObjectVolatile(ss, u);
218 | }
219 |
220 |
221 | // Hash-based segment and entry accesses
222 | /**
223 | * Get the segment for the given hash
224 | */
225 | @SuppressWarnings("unchecked")
226 | private CacheSegment segmentForHash(int h) {
227 | long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
228 | return (CacheSegment) UNSAFE.getObjectVolatile(segments, u);
229 | }
230 |
231 | // Unsafe mechanics
232 | private static final Unsafe UNSAFE;
233 | private static final long SBASE;
234 | private static final int SSHIFT;
235 |
236 | static {
237 | int ss;
238 | try {
239 | Field f = Unsafe.class.getDeclaredField("theUnsafe");
240 | f.setAccessible(true);
241 | UNSAFE = (Unsafe) f.get(null);
242 |
243 | Class sc = CacheSegment[].class;
244 | SBASE = UNSAFE.arrayBaseOffset(sc);
245 | ss = UNSAFE.arrayIndexScale(sc);
246 | } catch (Exception e) {
247 | throw new Error(e);
248 | }
249 | if ((ss & (ss-1)) != 0)
250 | throw new Error("data type scale not a power of two");
251 | SSHIFT = 31 - Integer.numberOfLeadingZeros(ss);
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/impl/HashCacheEntry.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache.impl;
2 |
3 | import com.spring2go.okcache.CacheEntry;
4 | import com.spring2go.okcache.ExpirationPolicy;
5 | import com.spring2go.okcache.TimeHelper;
6 | import sun.misc.Unsafe;
7 |
8 | import java.io.Serializable;
9 | import java.lang.reflect.Field;
10 |
11 | /**
12 | *
13 | * Created on Jul, 2020 by @author bobo
14 | */
15 | public class HashCacheEntry implements CacheEntry {
16 | final int hash;
17 |
18 | final K key;
19 | volatile V value;
20 | volatile long accessTime;
21 | volatile long writeTime;
22 | volatile long createTime;
23 | volatile long expireAfterAccess = -1l;
24 | volatile long expireAfterWrite = -1l;
25 | volatile long expireAfterCreate = -1l;
26 |
27 | transient volatile HashCacheEntry next = null;
28 | transient volatile HashCacheEntry previous = null;
29 | transient volatile HashCacheEntry nextAccess = null;
30 | transient volatile HashCacheEntry previousAccess = null;
31 |
32 | public HashCacheEntry(int hash, K key, V value, ExpirationPolicy expirationPolicy) {
33 | this(hash,key, value, expirationPolicy.getAfterAccess(), expirationPolicy.getAfterWrite(), expirationPolicy.getAfterCreate());
34 | }
35 |
36 | public HashCacheEntry(int hash,K key, V value, long expireAfterAccess, long expireAfterWrite, long expireAfterCreate) {
37 | this.hash = hash;
38 | this.key = key;
39 | this.value = value;
40 | this.expireAfterAccess = expireAfterAccess;
41 | this.expireAfterWrite = expireAfterWrite;
42 | this.expireAfterCreate = expireAfterCreate;
43 | accessTime = writeTime = createTime = TimeHelper.nowMs();
44 | }
45 |
46 | public K getKey() {
47 | return key;
48 | }
49 |
50 | public V getValue() {
51 | return value;
52 | }
53 |
54 | public void setValue(V value) {
55 | UNSAFE.putOrderedObject(this,valueOffset,value);
56 | }
57 |
58 | public long getAccessTime() {
59 | return accessTime;
60 | }
61 |
62 | public void setAccessTime(long time) {
63 | UNSAFE.putLong(this,accessTimeOffset,time);
64 | }
65 |
66 | public long getWriteTime() {
67 | return writeTime;
68 | }
69 |
70 | public void setWriteTime(long time) {
71 | UNSAFE.putLong(this,accessTimeOffset,time);
72 | UNSAFE.putLong(this,writeTimeOffset,time);
73 | }
74 |
75 | public long getCreateTime() {
76 | return createTime;
77 | }
78 |
79 | public void setCreateTime(long time) {
80 | UNSAFE.putLong(this,accessTimeOffset,time);
81 | UNSAFE.putLong(this,writeTimeOffset,time);
82 | UNSAFE.putLong(this,createTimeOffset,time);
83 | }
84 |
85 | public long getExpireAfterAccess() {
86 | return expireAfterAccess;
87 | }
88 |
89 | public void setExpireAfterAccess(long time) {
90 | UNSAFE.putLong(this,expireAfterAccessOffset,time);
91 | }
92 |
93 | public long getExpireAfterWrite() {
94 | return expireAfterWrite;
95 | }
96 |
97 | public void setExpireAfterWrite(long time) {
98 | UNSAFE.putLong(this,expireAfterWriteOffset,time);
99 | }
100 |
101 | public long getExpireAfterCreate() {
102 | return expireAfterCreate;
103 | }
104 |
105 | public void setExpireAfterCreate(long time) {
106 | UNSAFE.putLong(this,expireAfterCreateOffset,time);
107 | }
108 |
109 | public HashCacheEntry getNext() {
110 | return next;
111 | }
112 |
113 | public void setNext(HashCacheEntry next) {
114 | UNSAFE.putOrderedObject(this, nextOffset, next);
115 | }
116 |
117 | public HashCacheEntry getPrevious() {
118 | return previous;
119 | }
120 |
121 | public void setPrevious(HashCacheEntry previous) {
122 | UNSAFE.putOrderedObject(this, previousOffset, next);
123 | }
124 |
125 | public HashCacheEntry getNextInAccessQueue() {
126 | return nextAccess;
127 | }
128 |
129 | public void setNextInAccessQueue(HashCacheEntry next) {
130 | UNSAFE.putOrderedObject(this, nextAccessOffset,next);
131 | }
132 |
133 | public HashCacheEntry getPreviousInAccessQueue() {
134 | return previousAccess;
135 | }
136 |
137 | public void setPreviousInAccessQueue(HashCacheEntry previous) {
138 | UNSAFE.putOrderedObject(this, previousAccessOffset, previous);
139 | }
140 |
141 | static final Unsafe UNSAFE;
142 | static final long valueOffset;
143 | static final long accessTimeOffset;
144 | static final long writeTimeOffset;
145 | static final long createTimeOffset;
146 | static final long expireAfterAccessOffset;
147 | static final long expireAfterWriteOffset;
148 | static final long expireAfterCreateOffset;
149 |
150 | static final long nextOffset;
151 | static final long previousOffset;
152 | static final long nextAccessOffset;
153 | static final long previousAccessOffset;
154 |
155 | static {
156 | try {
157 | Field f = Unsafe.class.getDeclaredField("theUnsafe");
158 | f.setAccessible(true);
159 | UNSAFE = (Unsafe) f.get(null);
160 |
161 | Class k = HashCacheEntry.class;
162 | valueOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("value"));
163 | accessTimeOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("accessTime"));
164 | writeTimeOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("writeTime"));
165 | createTimeOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("createTime"));
166 | expireAfterAccessOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("expireAfterAccess"));
167 | expireAfterWriteOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("expireAfterWrite"));
168 | expireAfterCreateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("expireAfterCreate"));
169 |
170 | nextOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("next"));
171 | previousOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("previous"));
172 |
173 | nextAccessOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("nextAccess"));
174 | previousAccessOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("previousAccess"));
175 |
176 | } catch (Exception e) {
177 | throw new Error(e);
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/impl/SegmentAccessQueue.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache.impl;
2 |
3 | import com.spring2go.okcache.AbstractDeque;
4 |
5 | import java.util.Iterator;
6 | import java.util.NoSuchElementException;
7 |
8 | /**
9 | *
10 | * Created on Jul, 2020 by @author bobo
11 | */
12 | public class SegmentAccessQueue extends AbstractDeque {
13 | @Override
14 | public boolean offerFirst(HashCacheEntry entry) {
15 | if (entry == null) return false;
16 |
17 | if (entry.getNextInAccessQueue() != null)
18 | connectAccessOrder(entry.getPreviousInAccessQueue(), entry.getNextInAccessQueue());
19 |
20 | HashCacheEntry headNext = head.getNextInAccessQueue();
21 | connectAccessOrder(head, entry);
22 | connectAccessOrder(entry, headNext);
23 |
24 | return true;
25 | }
26 |
27 | @Override
28 | public boolean offerLast(HashCacheEntry entry) {
29 | if (entry == null) return false;
30 |
31 | if (entry.getNextInAccessQueue() != null)
32 | connectAccessOrder(entry.getPreviousInAccessQueue(), entry.getNextInAccessQueue());
33 | HashCacheEntry headPrevious = head.getPreviousInAccessQueue();
34 | connectAccessOrder(headPrevious, entry);
35 | connectAccessOrder(entry, head);
36 |
37 | return true;
38 | }
39 |
40 | @Override
41 | public HashCacheEntry pollFirst() {
42 | HashCacheEntry entry = head.getNextInAccessQueue();
43 | if (entry == head) return null;
44 | connectAccessOrder(head, entry.getNextInAccessQueue());
45 | nullifyAccessOrder(entry);
46 | return entry;
47 | }
48 |
49 | @Override
50 | public HashCacheEntry pollLast() {
51 | HashCacheEntry entry = head.getPreviousInAccessQueue();
52 | if (entry == head) return null;
53 | connectAccessOrder(entry.getPreviousInAccessQueue(), head);
54 | nullifyAccessOrder(entry);
55 | return entry;
56 | }
57 |
58 | @Override
59 | public HashCacheEntry peekFirst() {
60 | HashCacheEntry entry = head.getNextInAccessQueue();
61 | return entry == head ? null : entry;
62 | }
63 |
64 | @Override
65 | public HashCacheEntry peekLast() {
66 | HashCacheEntry entry = head.getPreviousInAccessQueue();
67 | return entry == head ? null : entry;
68 | }
69 |
70 | @Override
71 | public boolean remove(Object o) {
72 | HashCacheEntry entry = (HashCacheEntry) o;
73 | HashCacheEntry previous = entry.getPreviousInAccessQueue();
74 | HashCacheEntry next = entry.getNextInAccessQueue();
75 |
76 | if (previous == null) {
77 | return false;
78 | } else {
79 | connectAccessOrder(previous, next);
80 | nullifyAccessOrder(entry);
81 | return true;
82 | }
83 |
84 | }
85 |
86 | @Override
87 | public boolean contains(Object o) {
88 | HashCacheEntry entry = (HashCacheEntry) o;
89 | return entry.getNextInAccessQueue() != null;
90 | }
91 |
92 | @Override
93 | public boolean isEmpty() {
94 | return head.getNextInAccessQueue() == head;
95 | }
96 |
97 | @Override
98 | public void clear() {
99 | HashCacheEntry e, next;
100 | e = head.getNextInAccessQueue();
101 | while (e != head) {
102 | next = e.getNextInAccessQueue();
103 | nullifyAccessOrder(e);
104 | e = next;
105 | }
106 | connectAccessOrder(head, head);
107 | }
108 |
109 | @Override
110 | public Iterator iterator() {
111 | return new Iterator() {
112 | private HashCacheEntry next = peek();
113 |
114 | @Override
115 | public boolean hasNext() {
116 | return next != null;
117 | }
118 |
119 | @Override
120 | public HashCacheEntry next() {
121 | if (!hasNext()) {
122 | throw new NoSuchElementException();
123 | }
124 | try {
125 | return next;
126 | } finally {
127 | HashCacheEntry n = next().getNextInAccessQueue();
128 | next = n == head ? null : n;
129 | }
130 | }
131 |
132 | @Override
133 | public void remove() {
134 | throw new UnsupportedOperationException();
135 | }
136 | };
137 | }
138 |
139 | @Override
140 | public int size() {
141 | int size = 0;
142 | HashCacheEntry entry = head;
143 | while ((entry = entry.getNextInAccessQueue()) != head) size++;
144 | return size;
145 | }
146 |
147 | final HashCacheEntry head = new HeadHashCacheEntry();
148 |
149 |
150 | void connectAccessOrder(HashCacheEntry previous, HashCacheEntry next) {
151 | previous.setNextInAccessQueue(next);
152 | next.setPreviousInAccessQueue(previous);
153 | }
154 |
155 | void nullifyAccessOrder(HashCacheEntry nulled) {
156 | nulled.setNextInAccessQueue(null);
157 | nulled.setPreviousInAccessQueue(null);
158 | }
159 |
160 | final static class HeadHashCacheEntry extends HashCacheEntry {
161 | HeadHashCacheEntry() {
162 | super(0,null,null,0,0,0);
163 | previousAccess = this;
164 | nextAccess = this;
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/impl/SegmentStatsCounter.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache.impl;
2 |
3 | import com.spring2go.okcache.CacheStats;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public class SegmentStatsCounter {
9 | volatile long hitCount;
10 | volatile long missCount;
11 |
12 | volatile long memoryHitCount = 0l;
13 | volatile long memoryMissCount = 0l;
14 |
15 | volatile long createCount = 0l;
16 | volatile long updateCount = 0l;
17 | volatile long removeCount = 0l;
18 |
19 | volatile long evictCount = 0l;
20 | volatile long expireCount = 0l;
21 |
22 | volatile long evictStoreHitCount = 0l;
23 | volatile long evictStoreMissCount = 0l;
24 |
25 | volatile long behindStoreHitCount = 0l;
26 | volatile long behindStoreMissCount = 0l;
27 |
28 | volatile long size = 0l;
29 | volatile long memorySize = 0l;
30 |
31 | volatile long evictStoreExceptionCount = 0l;
32 | volatile long behindStoreExceptionCount = 0l;
33 |
34 | public void hits(int count) {
35 | hitCount += count;
36 | }
37 |
38 | public void misses(int count) {
39 | missCount += count;
40 | }
41 |
42 | public void memoryHits(int count) {
43 | memoryHitCount += count;
44 | }
45 |
46 | public void memoryMisses(int count) {
47 | memoryMissCount += count;
48 | }
49 |
50 | public void recordCreates(int count) {
51 | createCount += count;
52 | }
53 |
54 | public void recordUpdates(int count) {
55 | updateCount += count;
56 | }
57 |
58 | public void recordRemoves(int count) {
59 | removeCount+=count;
60 | }
61 |
62 | public void recordEvicts(int count) {
63 | evictCount += count;
64 | }
65 |
66 | public void recordExpires(int count) {
67 | expireCount+=count;
68 | }
69 |
70 | public void evictStoreHits(int count) {
71 | evictStoreHitCount+=count;
72 | }
73 |
74 | public void evictStoreMisses(int count) {
75 | evictStoreMissCount+=count;
76 | }
77 |
78 | public void behindStoreHits(int count) {
79 | behindStoreHitCount+=count;
80 | }
81 |
82 | public void behindStoreMisses(int count) {
83 | behindStoreMissCount+=count;
84 | }
85 |
86 | public void sizeIncrement() {
87 | size++;
88 | }
89 |
90 | public void sizeDecrement() {
91 | size--;
92 | }
93 |
94 | public void memorySizeIncrement() {
95 | memorySize++;
96 | }
97 |
98 | public void memorySizeDecrement() {
99 | memorySize--;
100 | }
101 |
102 | public void evictStoreException(int count){
103 | evictStoreExceptionCount += count;
104 | }
105 |
106 | public void behindStoreException(int count){
107 | behindStoreExceptionCount += count;
108 | }
109 |
110 | public CacheStats snapshot() {
111 | return new CacheStats(
112 | hitCount,
113 | missCount,
114 | memoryHitCount,
115 | memoryMissCount,
116 | createCount,
117 | updateCount,
118 | removeCount,
119 | evictCount,
120 | expireCount,
121 | evictStoreHitCount,
122 | evictStoreMissCount,
123 | behindStoreHitCount,
124 | behindStoreMissCount,
125 | size,
126 | memorySize
127 | );
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/com/spring2go/okcache/store/BigCacheStore.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache.store;
2 |
3 | import com.spring2go.bigcache.BigCache;
4 | import com.spring2go.okcache.*;
5 |
6 | import java.io.IOException;
7 | import java.io.Serializable;
8 |
9 | /**
10 | * Created on Jul, 2020 by @author bobo
11 | */
12 | public class BigCacheStore implements CacheStore {
13 | private BigCache cache;
14 | private Serializer serializer = new JavaSerializer();
15 |
16 | public BigCacheStore(BigCache cache) {
17 | this.cache = cache;
18 | }
19 |
20 | @Override
21 | public void store(CacheEntry entry) throws Exception {
22 | if (entry == null) {
23 | return;
24 | }
25 |
26 | K key = entry.getKey();
27 | if (key == null) {
28 | return;
29 | }
30 |
31 | byte[] value= serializer.serialize(entry);
32 | if (value == null || value.length == 0) {
33 | return;
34 | }
35 |
36 | long ttl = CacheEntryHelper.calculateTTL(entry, TimeHelper.nowMs());
37 | cache.put(key, value, ttl);
38 | }
39 |
40 | @Override
41 | public CacheEntry load(K key) throws Exception {
42 | return remove(key);
43 | }
44 |
45 | @Override
46 | public CacheEntry remove(K key) throws Exception {
47 | if (key == null) {
48 | return null;
49 | }
50 |
51 | byte[] value = cache.delete(key);
52 | if (value == null) {
53 | return null;
54 | }
55 |
56 | return serializer.deserialize(value, CacheEntry.class);
57 |
58 | }
59 |
60 | @Override
61 | public void close() {
62 | try {
63 | cache.close();
64 | } catch (IOException e) {
65 | e.printStackTrace();
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCacheLimitTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.Date;
6 |
7 | import com.spring2go.bigcache.CacheConfig.StorageMode;
8 | import com.spring2go.bigcache.utils.FileUtil;
9 | import com.spring2go.bigcache.utils.TestUtil;
10 |
11 | /**
12 | * Created on Jul, 2020 by @author bobo
13 | */
14 | public class BigCacheLimitTest {
15 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "stress/bigcache/";
16 |
17 | private static BigCache cache;
18 |
19 | public static void main(String args[]) throws IOException {
20 | CacheConfig config = new CacheConfig()
21 | .setStorageMode(StorageMode.OffHeapPlusFile)
22 | .setPurgeInterval(2 * 1000)
23 | .setMergeInterval(2 * 1000)
24 | .setMaxOffHeapMemorySize(10 * 1000 * 1024 * 1024L);
25 |
26 | cache = new BigCache(TEST_DIR, config);
27 |
28 | String rndString = TestUtil.randomString(10);
29 |
30 | System.out.println("Start from date " + new Date());
31 | long start = System.currentTimeMillis();
32 | for (long counter = 0;; counter++) {
33 | cache.put(Long.toString(counter), rndString.getBytes());
34 | if (counter % 1000000 == 0) {
35 | System.out.println("Current date " + new Date());
36 | System.out.println("" + counter);
37 | System.out.println(TestUtil.printMemoryFootprint());
38 | long end = System.currentTimeMillis();
39 | System.out.println("timeSpent = " + (end - start));
40 | try {
41 | Thread.sleep(5000);
42 | } catch (InterruptedException e) {
43 | }
44 | start = System.currentTimeMillis();
45 | }
46 | }
47 | }
48 |
49 | public static void close() throws IOException {
50 | if (cache == null)
51 | return;
52 | try {
53 | cache.close();
54 | FileUtil.deleteDirectory(new File(TEST_DIR));
55 | } catch (IllegalStateException e) {
56 | System.gc();
57 | try {
58 | FileUtil.deleteDirectory(new File(TEST_DIR));
59 | } catch (IllegalStateException e1) {
60 | try {
61 | Thread.sleep(3000);
62 | } catch (InterruptedException e2) {
63 | }
64 | FileUtil.deleteDirectory(new File(TEST_DIR));
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCachePerfTestA.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.List;
11 | import java.util.concurrent.ExecutionException;
12 | import java.util.concurrent.ExecutorService;
13 | import java.util.concurrent.Executors;
14 | import java.util.concurrent.Future;
15 |
16 | import org.junit.After;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 | import org.junit.runners.Parameterized;
20 | import org.junit.runners.Parameterized.Parameter;
21 | import org.junit.runners.Parameterized.Parameters;
22 |
23 | import com.spring2go.bigcache.CacheConfig.StorageMode;
24 | import com.spring2go.bigcache.utils.FileUtil;
25 | import com.spring2go.bigcache.utils.TestSample;
26 | import com.spring2go.bigcache.utils.TestUtil;
27 |
28 | /**
29 | * Created on Jul, 2020 by @author bobo
30 | */
31 | @RunWith(Parameterized.class)
32 | public class BigCachePerfTestA {
33 | private static final int THREAD_COUNT = 128;
34 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "performance/bigcache/";
35 |
36 | private static BigCache cache;
37 |
38 | @Parameter(value = 0)
39 | public StorageMode storageMode;
40 |
41 | @Parameters
42 | public static Collection data() throws IOException {
43 | StorageMode[][] data = { { StorageMode.PureFile },
44 | { StorageMode.MemoryMappedPlusFile },
45 | { StorageMode.OffHeapPlusFile } };
46 | return Arrays.asList(data);
47 | }
48 |
49 | private BigCache cache() throws IOException {
50 | CacheConfig config = new CacheConfig();
51 | config.setStorageMode(storageMode)
52 | .setCapacityPerBlock(20 * 1024 * 1024)
53 | .setMergeInterval(2 * 1000)
54 | .setPurgeInterval(2 * 1000);
55 | BigCache cache = new BigCache(TEST_DIR, config);
56 | return cache;
57 | }
58 |
59 | @Test
60 | public void testSingleThreadReadWrite() throws IOException, ClassNotFoundException {
61 | final int count = 400 * 1000;
62 | final TestSample sample = new TestSample();
63 |
64 | cache = cache();
65 |
66 | long start = System.nanoTime();
67 |
68 | StringBuilder user = new StringBuilder();
69 | for (int i = 0; i < count; i++) {
70 | sample.intA = i;
71 | sample.doubleA = i;
72 | sample.longA = i;
73 | cache.put(TestSample.users(user, i), sample.toBytes());
74 | }
75 | for (int i = 0; i < count; i++) {
76 | byte[] result = cache.get(TestSample.users(user, i));
77 | assertNotNull(result);
78 | }
79 | for (int i = 0; i < count; i++) {
80 | byte[] result = cache.get(TestSample.users(user, i));
81 | TestSample re = TestSample.fromBytes(result);
82 | assertEquals(i, re.intA);
83 | assertEquals(i, re.doubleA, 0.0);
84 | assertEquals(i, re.longA);
85 | }
86 | for (int i = 0; i < count; i++) {
87 | cache.delete(TestSample.users(user, i));
88 | }
89 | assertEquals(cache.count(), 0);
90 | long duration = System.nanoTime() - start;
91 | System.out.printf("Put/get %,d K operations per second%n",
92 | (int) (count * 4 * 1e6 / duration));
93 | }
94 |
95 | @Test
96 | public void testMultiThreadReadWrite() throws InterruptedException, ExecutionException, IOException {
97 | final int count = 2 * 1000 * 1000;
98 | ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
99 |
100 | cache = cache();
101 |
102 | long start = System.nanoTime();
103 | List> futures = new ArrayList>();
104 | for (int i = 0; i < THREAD_COUNT; i++) {
105 | final int finalI = i;
106 | futures.add(service.submit(new Runnable() {
107 |
108 | @Override
109 | public void run() {
110 | try {
111 | final TestSample sample = new TestSample();
112 | StringBuilder user = new StringBuilder();
113 | for (int j = finalI; j < count; j += THREAD_COUNT) {
114 | sample.intA = j;
115 | sample.doubleA = j;
116 | sample.longA = j;
117 | cache.put(TestSample.users(user, j), sample.toBytes());
118 | }
119 | for (int j = finalI; j < count; j += THREAD_COUNT) {
120 | byte[] result = cache.get(TestSample.users(user, j));
121 | assertNotNull(result);
122 | }
123 | for (int j = finalI; j < count; j += THREAD_COUNT) {
124 | byte[] result = cache.get(TestSample.users(user, j));
125 | TestSample re = TestSample.fromBytes(result);
126 | assertEquals(j, re.intA);
127 | assertEquals(j, re.doubleA, 0.0);
128 | assertEquals(j, re.longA);
129 | }
130 | for (int j = finalI; j < count; j += THREAD_COUNT) {
131 | cache.delete(TestSample.users(user, j));
132 | }
133 | } catch (IOException e) {
134 | e.printStackTrace();
135 | } catch (ClassNotFoundException e1) {
136 | e1.printStackTrace();
137 | }
138 | }
139 |
140 | }));
141 | }
142 |
143 | for (Future> future : futures) {
144 | future.get();
145 | }
146 |
147 | long duration = System.nanoTime() - start;
148 | System.out.printf("Put/get %,d K operations per second%n",
149 | (int) (count * 4 * 1e6 / duration));
150 | service.shutdown();
151 | }
152 |
153 | @After
154 | public void close() throws IOException {
155 | try {
156 | cache.close();
157 | FileUtil.deleteDirectory(new File(TEST_DIR));
158 | } catch (IllegalStateException e) {
159 | System.gc();
160 | try {
161 | FileUtil.deleteDirectory(new File(TEST_DIR));
162 | } catch (IllegalStateException e1) {
163 | try {
164 | Thread.sleep(3000);
165 | } catch (InterruptedException e2) {
166 | }
167 | FileUtil.deleteDirectory(new File(TEST_DIR));
168 | }
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCacheReadWriteStressTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.List;
11 | import java.util.concurrent.ExecutionException;
12 | import java.util.concurrent.ExecutorService;
13 | import java.util.concurrent.Executors;
14 | import java.util.concurrent.Future;
15 |
16 | import org.junit.After;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 | import org.junit.runners.Parameterized;
20 | import org.junit.runners.Parameterized.Parameter;
21 | import org.junit.runners.Parameterized.Parameters;
22 |
23 | import com.spring2go.bigcache.CacheConfig.StorageMode;
24 | import com.spring2go.bigcache.utils.FileUtil;
25 | import com.spring2go.bigcache.utils.TestSample;
26 | import com.spring2go.bigcache.utils.TestUtil;
27 |
28 | /**
29 | * Created on Jul, 2020 by @author bobo
30 | */
31 | @RunWith(Parameterized.class)
32 | public class BigCacheReadWriteStressTest {
33 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "stress/bigcache/";
34 |
35 | private static BigCache cache;
36 |
37 | @Parameter(value = 0)
38 | public StorageMode storageMode;
39 |
40 | @Parameters
41 | public static Collection data() throws IOException {
42 | StorageMode[][] data = { //{ StorageMode.PureFile },
43 | { StorageMode.MemoryMappedPlusFile },
44 | { StorageMode.OffHeapPlusFile } };
45 | return Arrays.asList(data);
46 | }
47 |
48 | public BigCache cache(long count) throws IOException {
49 | CacheConfig config = new CacheConfig();
50 | config.setStorageMode(storageMode);
51 | BigCache cache = new BigCache(TEST_DIR, config);
52 |
53 | for (long i = 0; i < count; i++) {
54 | String key = "" + i;
55 | cache.put(key, key.getBytes());
56 | }
57 | return cache;
58 | }
59 |
60 | @Test
61 | public void testWrite_ten_million() throws IOException {
62 | final long item_count = 10 * 1000 * 1000;
63 | long elapsedTime = 0;
64 | long count = 0;
65 | cache = cache(0);
66 | long startTime = System.nanoTime();
67 |
68 | for (long i = 0; i < item_count; i++) {
69 | String key = "" + i;
70 | long start = System.nanoTime();
71 | cache.put(key, key.getBytes());
72 |
73 | if (i % (100 * 1000) == 0) {
74 | elapsedTime += (System.nanoTime() - start);
75 | count++;
76 | }
77 | }
78 |
79 | System.out.println("avg:" + elapsedTime / count + " ns");
80 | System.out.println("write " + item_count / (1000 * 1000) + " million times:"
81 | + (System.nanoTime() - startTime) / (1000 * 1000) + " ms");
82 | }
83 |
84 | @Test
85 | public void testRead_ten_million() throws IOException {
86 | final long item_count = 10 * 1000 * 1000;
87 | cache = cache(item_count);
88 | long startTime = System.nanoTime();
89 |
90 | for (long i = 0; i < item_count; i++) {
91 | String key = "" + i;
92 | cache.get(key);
93 | }
94 | System.out.println("read " + item_count / (1000 * 1000) + " million times:" + (System.nanoTime() - startTime)
95 | / (1000 * 1000)
96 | + " ms");
97 | }
98 |
99 | @Test
100 | public void testReadWrite_one_million() throws IOException {
101 | final long item_count = 1000 * 1000;
102 | final int keyLen = 8;
103 | final int valueLen = 128;
104 | this.executeReadWrite(item_count, keyLen, valueLen);
105 | }
106 |
107 | @Test
108 | public void testReadWrite_two_million() throws IOException {
109 | final long item_count = 2 * 1000 * 1000;
110 | final int keyLen = 8;
111 | final int valueLen = 1024;
112 | this.executeReadWrite(item_count, keyLen, valueLen);
113 | }
114 |
115 | private void executeReadWrite(final long count, int keyLen, int valueLen) throws IOException {
116 | final int defaultKeyLen = 8;
117 | final int defaultValueLen = 32;
118 |
119 | CacheConfig config = new CacheConfig();
120 | config.setStorageMode(storageMode);
121 | cache = new BigCache(TEST_DIR, config);
122 | List keys = new ArrayList();
123 |
124 | String key = "";
125 | String value = "";
126 | if (keyLen > 0) {
127 | key = TestUtil.randomString(keyLen);
128 | } else {
129 | key = TestUtil.randomString(defaultKeyLen);
130 | }
131 | if (valueLen > 0) {
132 | value = TestUtil.randomString(valueLen);
133 | } else {
134 | value = TestUtil.randomString(defaultValueLen);
135 | }
136 | for (int i = 0; i < count; i++) {
137 | cache.put(key + i, TestUtil.getBytes(value + i));
138 | keys.add(key + i);
139 | }
140 | assertEquals(cache.count(), count);
141 | for (String k : keys) {
142 | String v = new String(cache.get(k));
143 | String index = k.substring(keyLen > 0 ? keyLen : 8);
144 | assertEquals(value + index, v);
145 | }
146 | for (String k : keys) {
147 | cache.delete(k);
148 | }
149 | for (String k : keys) {
150 | assertNull(cache.get(k));
151 | }
152 | }
153 |
154 | @Test
155 | public void testMultiThreadWriteTtl_two_million() throws InterruptedException, ExecutionException, IOException {
156 | final int count = 2 * 1000 * 1000;
157 | final int threadCount = 16;
158 | ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
159 |
160 | CacheConfig config = new CacheConfig();
161 | config.setStorageMode(storageMode)
162 | .setCapacityPerBlock(20 * 1024 * 1024)
163 | .setMergeInterval(2 * 1000)
164 | .setPurgeInterval(2 * 1000);
165 | cache = new BigCache(TEST_DIR, config);
166 |
167 | long start = System.nanoTime();
168 | List> futures = new ArrayList>();
169 | for (int i = 0; i < threadCount; i++) {
170 | final int finalI = i;
171 | futures.add(service.submit(new Runnable() {
172 |
173 | @Override
174 | public void run() {
175 | try {
176 | final TestSample sample = new TestSample();
177 | StringBuilder user = new StringBuilder();
178 | for (int j = finalI; j < count; j += threadCount) {
179 | sample.intA = j;
180 | sample.doubleA = j;
181 | sample.longA = j;
182 | cache.put(TestSample.users(user, j), sample.toBytes(), 2 * 1000);
183 | }
184 | Thread.sleep(10 * 1000);
185 | for (int j = finalI; j < count; j += threadCount) {
186 | assertNull(cache.get(TestSample.users(user, j)));
187 | }
188 | } catch (IOException e) {
189 | e.printStackTrace();
190 | } catch (InterruptedException e) {
191 | e.printStackTrace();
192 | }
193 | }
194 |
195 | }));
196 | }
197 |
198 | for (Future> future : futures) {
199 | future.get();
200 | }
201 |
202 | long duration = System.nanoTime() - start;
203 | System.out.printf("Put/get %,d K operations per second%n",
204 | (int) (count * 4 * 1e6 / duration));
205 | service.shutdown();
206 | }
207 |
208 | @After
209 | public void close() throws IOException {
210 | try {
211 | cache.close();
212 | FileUtil.deleteDirectory(new File(TEST_DIR));
213 | } catch (IllegalStateException e) {
214 | System.gc();
215 | try {
216 | FileUtil.deleteDirectory(new File(TEST_DIR));
217 | } catch (IllegalStateException e1) {
218 | try {
219 | Thread.sleep(10000);
220 | } catch (InterruptedException e2) {
221 | }
222 | FileUtil.deleteDirectory(new File(TEST_DIR));
223 | }
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCacheStressTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import java.util.Date;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 | import java.util.Random;
7 |
8 | import com.spring2go.bigcache.CacheConfig.StorageMode;
9 | import com.spring2go.bigcache.utils.TestUtil;
10 |
11 | /**
12 | * Created on Jul, 2020 by @author bobo
13 | */
14 | public class BigCacheStressTest {
15 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "stress/bigcache/";
16 |
17 | private static BigCache cache;
18 |
19 | public static void main(String[] args) throws Exception {
20 | int numKeyLimit = 1024 * 16;
21 | int valueLengthLimit = 1024 * 16;
22 |
23 | CacheConfig config = new CacheConfig();
24 | config.setStorageMode(StorageMode.OffHeapPlusFile)
25 | .setPurgeInterval(2 * 1000)
26 | .setMergeInterval(2 * 1000)
27 | //allocate half of the memory - leave space for keys and reference map.
28 | .setMaxOffHeapMemorySize(5 * 1000 * 1024 * 1024L);
29 | cache = new BigCache(TEST_DIR, config);
30 | Map map = new HashMap();
31 |
32 | String[] rndStrings = { TestUtil.randomString(valueLengthLimit / 2),
33 | TestUtil.randomString(valueLengthLimit),
34 | TestUtil.randomString(valueLengthLimit + valueLengthLimit / 2) };
35 | byte[] rndBytes = rndStrings[1].getBytes();
36 |
37 | Random random = new Random();
38 |
39 | System.out.println("Start from date " + new Date());
40 | long start = System.currentTimeMillis();
41 | for (long counter = 0;; counter++) {
42 | int rndKey = random.nextInt(numKeyLimit);
43 | boolean put = random.nextDouble() < 0.5 ? true : false;
44 | if (put) {
45 | rndBytes = rndStrings[random.nextInt(3)].getBytes();
46 | map.put(String.valueOf(rndKey), rndBytes);
47 | cache.put(String.valueOf(rndKey), rndBytes);
48 | } else {
49 | map.remove(String.valueOf(rndKey));
50 | byte[] oldV = cache.delete(String.valueOf(rndKey));
51 | byte[] v = cache.get(String.valueOf(rndKey));
52 | if (v != null) {
53 | System.out.println("should be null. Key:" + String.valueOf(rndKey) + " Value:" + new String(v));
54 | System.out.println(" Key:" + String.valueOf(rndKey) + " oldValue:"
55 | + (oldV == null ? null : new String(oldV)));
56 | }
57 | }
58 |
59 | cache.put(counter + "-ttl", rndBytes, (long) 10 * 1000);
60 |
61 | if (counter % 1000000 == 0) {
62 | System.out.println("Current date: " + new Date());
63 | System.out.println("counter: " + counter);
64 | System.out.println("purge: " + cache.getStats().getCacheExpire());
65 | System.out.println("move: " + cache.getStats().getCacheMove());
66 | System.out.println("size: " + cache.count());
67 | long cacheUsed = cache.getStats().getStorageUsed();
68 | System.out.println("used: " + cacheUsed);
69 | System.out.println();
70 |
71 | long storeUsed = cache.storageManager.getUsed();
72 | if (cacheUsed != storeUsed) {
73 | System.out.println("!!!! Temporarily fail the test, this could seldom occur");
74 | System.out.println("storage used: " + storeUsed + ", but cache used: " + cacheUsed);
75 | }
76 |
77 | System.out.println();
78 | System.out.println(TestUtil.printMemoryFootprint());
79 | long end = System.currentTimeMillis();
80 | System.out.println("timeSpent = " + (end - start));
81 | System.out.println("ttl count = " + (cache.count() - map.size()));
82 | System.out.println("used size = " + cache.getStats().getStorageUsed());
83 |
84 | // validation
85 | for (int i = 0; i < numKeyLimit; i++) {
86 | String key = String.valueOf(i);
87 | byte[] mapValue = map.get(key);
88 | byte[] cacheValue = cache.get(key);
89 |
90 | if (mapValue == null && cacheValue != null) {
91 | System.out.println("Key:" + key);
92 | System.out.println("Value:" + new String(cacheValue));
93 | throw new RuntimeException("Validation exception, key exists in cache but not in map");
94 | }
95 | if (mapValue != null && cacheValue == null) {
96 | throw new RuntimeException("Validation exception, key exists in map but not in cache");
97 | }
98 | if (mapValue != null && cacheValue != null) {
99 | if (compare(mapValue, cacheValue) != 0) {
100 | throw new RuntimeException("Validation exception, values in map and cache does not equal");
101 | }
102 | }
103 | }
104 |
105 |
106 | //exclude validation process from time count
107 | start = System.currentTimeMillis();
108 | }
109 | }
110 | }
111 |
112 | public static int compare(byte[] left, byte[] right) {
113 | for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) {
114 | int a = (left[i] & 0xff);
115 | int b = (right[j] & 0xff);
116 | if (a != b) {
117 | return a - b;
118 | }
119 | }
120 | return left.length - right.length;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCacheTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertNotNull;
5 | import static org.junit.Assert.assertNull;
6 |
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.util.Arrays;
10 | import java.util.Collection;
11 | import java.util.HashSet;
12 | import java.util.Set;
13 |
14 | import org.junit.After;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 | import org.junit.runners.Parameterized;
18 | import org.junit.runners.Parameterized.Parameter;
19 | import org.junit.runners.Parameterized.Parameters;
20 |
21 | import com.spring2go.bigcache.CacheConfig.StorageMode;
22 | import com.spring2go.bigcache.utils.FileUtil;
23 | import com.spring2go.bigcache.utils.TestUtil;
24 |
25 | /**
26 | * Created on Jul, 2020 by @author bobo
27 | */
28 | @RunWith(Parameterized.class)
29 | public class BigCacheTest {
30 | private static final double STRESS_FACTOR = Double.parseDouble(System.getProperty("STRESS_FACTOR", "1.0"));
31 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "function/bigcache/";
32 |
33 | private BigCache cache;
34 |
35 | @Parameter(value = 0)
36 | public StorageMode storageMode;
37 |
38 | @Parameters
39 | public static Collection data() throws IOException {
40 | StorageMode[][] data = { { StorageMode.PureFile },
41 | { StorageMode.MemoryMappedPlusFile },
42 | { StorageMode.OffHeapPlusFile } };
43 | return Arrays.asList(data);
44 | }
45 |
46 | @Test
47 | public void testBigCache() throws IOException {
48 | CacheConfig config = new CacheConfig();
49 | config.setStorageMode(storageMode);
50 | cache = new BigCache(TEST_DIR, config);
51 | Set rndStringSet = new HashSet();
52 | for (int i = 0; i < 2000000 * STRESS_FACTOR; i++) {
53 | String rndString = TestUtil.randomString(64);
54 | rndStringSet.add(rndString);
55 | cache.put(rndString, rndString.getBytes());
56 | if ((i % 50000) == 0 && i != 0) {
57 | System.out.println(i + " rows written");
58 | }
59 | }
60 |
61 | for (String rndString : rndStringSet) {
62 | byte[] value = cache.get(rndString);
63 | assertNotNull(value);
64 | assertEquals(rndString, new String(value));
65 | }
66 |
67 | // delete
68 | for (String rndString : rndStringSet) {
69 | cache.delete(rndString);
70 | }
71 |
72 | for (String rndString : rndStringSet) {
73 | byte[] value = cache.get(rndString);
74 | assertNull(value);
75 | }
76 | }
77 |
78 | // @Test(expected = IllegalArgumentException.class)
79 | // public void testInvalidFileDir() {
80 | // String fakeDir = "dltestDB://bigcache_test/asdl";
81 | // CacheConfig config = new CacheConfig();
82 | // try {
83 | // cache = new BigCache(fakeDir, config);
84 | // } catch (IOException e) {
85 | // }
86 | // }
87 |
88 | @Test(expected = IllegalArgumentException.class)
89 | public void testInvalidCacheConfig() {
90 | CacheConfig config = new CacheConfig().setCapacityPerBlock(12)
91 | .setConcurrencyLevel(2345)
92 | .setInitialNumberOfBlocks(27);
93 | }
94 |
95 | @After
96 | public void close() throws IOException {
97 | if (cache == null)
98 | return;
99 | try {
100 | cache.close();
101 | FileUtil.deleteDirectory(new File(TEST_DIR));
102 | } catch (IllegalStateException e) {
103 | System.gc();
104 | try {
105 | FileUtil.deleteDirectory(new File(TEST_DIR));
106 | } catch (IllegalStateException e1) {
107 | try {
108 | Thread.sleep(3000);
109 | } catch (InterruptedException e2) {
110 | }
111 | FileUtil.deleteDirectory(new File(TEST_DIR));
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/BigCacheUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 |
10 | import org.junit.After;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.junit.runners.Parameterized;
14 | import org.junit.runners.Parameterized.Parameter;
15 | import org.junit.runners.Parameterized.Parameters;
16 |
17 | import com.spring2go.bigcache.CacheConfig.StorageMode;
18 | import com.spring2go.bigcache.utils.FileUtil;
19 | import com.spring2go.bigcache.utils.TestUtil;
20 |
21 | /**
22 | * Created on Jul, 2020 by @author bobo
23 | */
24 | @RunWith(Parameterized.class)
25 | public class BigCacheUnitTest {
26 | private static String TEST_DIR = TestUtil.TEST_BASE_DIR + "unit/bigcache/";
27 | private static BigCache cache;
28 |
29 | @Parameter(value = 0)
30 | public StorageMode storageMode;
31 |
32 | @Parameters
33 | public static Collection data() throws IOException {
34 | StorageMode[][] data = { { StorageMode.PureFile },
35 | { StorageMode.MemoryMappedPlusFile },
36 | { StorageMode.OffHeapPlusFile } };
37 | return Arrays.asList(data);
38 | }
39 |
40 | public BigCache cache6() throws IOException {
41 | CacheConfig config = new CacheConfig();
42 | config.setStorageMode(storageMode);
43 | BigCache cache = new BigCache(TEST_DIR, config);
44 | cache.put(0, "A".getBytes());
45 | cache.put(1, "B".getBytes());
46 | cache.put(2, "C".getBytes());
47 | cache.put(3, "D".getBytes());
48 | cache.put(4, "E".getBytes());
49 | cache.put(5, "F".getBytes());
50 | return cache;
51 | }
52 |
53 | @Test
54 | public void testGet() throws IOException {
55 | cache = cache6();
56 | assertEquals(new String(cache.get(0)), "A");
57 | assertEquals(new String(cache.get(1)), "B");
58 | assertEquals(new String(cache.get(2)), "C");
59 | assertEquals(new String(cache.get(3)), "D");
60 | assertEquals(new String(cache.get(4)), "E");
61 | assertEquals(new String(cache.get(5)), "F");
62 | }
63 |
64 | @Test
65 | public void testPut() throws IOException {
66 | cache = cache6();
67 | //test put new
68 | cache.put(6, "G".getBytes());
69 | assertEquals(new String(cache.get(6)), "G");
70 | //test replace old
71 | cache.put(0, "W".getBytes());
72 | assertEquals(new String(cache.get(0)), "W");
73 | }
74 |
75 | @Test
76 | public void testDelete() throws IOException {
77 | cache = cache6();
78 | byte[] old = cache.delete(0);
79 | assertEquals(new String(old), "A");
80 | assertNull(cache.get(0));
81 |
82 | old = cache.delete(6);
83 | assertNull(old);
84 | assertNull(cache.get(6));
85 | }
86 |
87 | @Test
88 | public void testContain() throws IOException {
89 | cache = cache6();
90 | assertTrue(cache.contains(0));
91 | assertFalse(cache.contains(6));
92 | }
93 |
94 | @After
95 | public void close() throws IOException {
96 | try {
97 | cache.close();
98 | FileUtil.deleteDirectory(new File(TEST_DIR));
99 | } catch (IllegalStateException e) {
100 | System.gc();
101 | try {
102 | FileUtil.deleteDirectory(new File(TEST_DIR));
103 | } catch (IllegalStateException e1) {
104 | try {
105 | Thread.sleep(3000);
106 | } catch (InterruptedException e2) {
107 | }
108 | FileUtil.deleteDirectory(new File(TEST_DIR));
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/storage/FileChannelStorageTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import com.spring2go.bigcache.utils.FileUtil;
4 | import com.spring2go.bigcache.utils.TestUtil;
5 | import org.junit.After;
6 | import org.junit.Test;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | import static org.junit.Assert.assertEquals;
12 |
13 | /**
14 | * Created on Jul, 2020 by @author bobo
15 | */
16 | public class FileChannelStorageTest {
17 | private static String testDir = TestUtil.TEST_BASE_DIR + "unit/file_channel_storage_test/";
18 |
19 | private IStorage fileChannelStorage = null;
20 |
21 | @Test
22 | public void testFileChannelStorage() throws IOException {
23 | fileChannelStorage = new FileChannelStorage(testDir, 1, 16 * 1024 * 1024);
24 |
25 | // test one item
26 | String testString = "Test String";
27 | fileChannelStorage.put(0, testString.getBytes());
28 | byte[] dest = new byte[testString.getBytes().length];
29 | fileChannelStorage.get(0, dest);
30 |
31 | assertEquals(testString, new String(dest));
32 |
33 | // test limit items
34 | int limit = 1000;
35 | int[] positionArray = new int[limit];
36 | int[] lengthArray = new int[limit];
37 | int position = 0;
38 | for(int i = 0; i < limit; i++) {
39 | byte[] src = (testString + i).getBytes();
40 | positionArray[i] = position;
41 | lengthArray[i] = src.length;
42 | fileChannelStorage.put(position, src);
43 | position += src.length;
44 | }
45 |
46 | for(int i = 0; i < limit; i++) {
47 | dest = new byte[lengthArray[i]];
48 | fileChannelStorage.get(positionArray[i], dest);
49 | assertEquals(testString + i, new String(dest));
50 | }
51 | }
52 |
53 | @After
54 | public void clear() throws IOException {
55 | if (this.fileChannelStorage != null) {
56 | this.fileChannelStorage.close();
57 | }
58 | FileUtil.deleteDirectory(new File(testDir));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/storage/StorageUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.storage;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 |
10 | import org.junit.After;
11 | import org.junit.AfterClass;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.junit.runners.Parameterized;
15 | import org.junit.runners.Parameterized.Parameter;
16 | import org.junit.runners.Parameterized.Parameters;
17 |
18 | import com.spring2go.bigcache.utils.FileUtil;
19 | import com.spring2go.bigcache.utils.TestUtil;
20 |
21 | /**
22 | * Created on Jul, 2020 by @author bobo
23 | */
24 | @RunWith(Parameterized.class)
25 | public class StorageUnitTest {
26 | private static final String TEST_DIR = TestUtil.TEST_BASE_DIR + "unit/istorage_test/";
27 | private static IStorage[][] storageData;
28 |
29 | @Parameter(value = 0)
30 | public IStorage storage;
31 |
32 | @Parameters
33 | public static Collection data() throws IOException {
34 | storageData = new IStorage[][] { { new FileChannelStorage(TEST_DIR, 0, 16 * 1024 * 1024) },
35 | { new MemoryMappedStorage(TEST_DIR, 0, 16 * 1024 * 1024) },
36 | { new OffHeapStorage(16 * 1024 * 1024) } };
37 | return Arrays.asList(storageData);
38 | }
39 |
40 | public void storage6() throws IOException {
41 | storage.put(0, "A".getBytes());
42 | storage.put(1, "B".getBytes());
43 | storage.put(2, "C".getBytes());
44 | storage.put(3, "D".getBytes());
45 | storage.put(4, "E".getBytes());
46 | storage.put(5, "F".getBytes());
47 | }
48 |
49 | @Test
50 | public void testGet() throws IOException {
51 | storage6();
52 | byte[] dest = new byte["A".getBytes().length];
53 | storage.get(0, dest);
54 | assertEquals(new String(dest), "A");
55 | storage.get(1, dest);
56 | assertEquals(new String(dest), "B");
57 | storage.get(2, dest);
58 | assertEquals(new String(dest), "C");
59 | storage.get(3, dest);
60 | assertEquals(new String(dest), "D");
61 | storage.get(4, dest);
62 | assertEquals(new String(dest), "E");
63 | storage.get(5, dest);
64 | assertEquals(new String(dest), "F");
65 | }
66 |
67 | @Test
68 | public void testPut() throws IOException {
69 | storage6();
70 | //test put new
71 | storage.put(6, "G".getBytes());
72 | byte[] dest = new byte["A".getBytes().length];
73 | storage.get(6, dest);
74 | assertEquals(new String(dest), "G");
75 | //test replace old
76 | storage.put(0, "W".getBytes());
77 | storage.get(0, dest);
78 | assertEquals(new String(dest), "W");
79 | }
80 |
81 | @After
82 | public void clear() throws IOException {
83 | storage.free();
84 | }
85 |
86 | @AfterClass
87 | public static void tearDown() throws IOException {
88 | for (IStorage[] storage : storageData) {
89 | storage[0].close();
90 | }
91 | try {
92 | FileUtil.deleteDirectory(new File(TEST_DIR));
93 | } catch (IllegalStateException e) {
94 | System.gc();
95 | try {
96 | FileUtil.deleteDirectory(new File(TEST_DIR));
97 | } catch (IllegalStateException e1) {
98 | try {
99 | Thread.sleep(3000);
100 | } catch (InterruptedException e2) {
101 | }
102 | FileUtil.deleteDirectory(new File(TEST_DIR));
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/utils/TestSample.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.utils;
2 |
3 | import java.io.*;
4 |
5 | /**
6 | * Created on Jul, 2020 by @author bobo
7 | */
8 | public class TestSample implements Serializable {
9 | private static final long serialVersionUID = 1L;
10 | public String stringA = "aaaaaaaaaa";
11 | public String stringB = "bbbbbbbbbb";
12 | public BuySell enumA = BuySell.Buy;
13 | public BuySell enumB = BuySell.Sell;
14 | public int intA = 123456;
15 | public int intB = 654321;
16 | public double doubleA = 1.23456789;
17 | public double doubleB = 9.87654321;
18 | public long longA = 987654321;
19 | public long longB = 123456789;
20 |
21 | public byte[] toBytes() throws IOException {
22 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
23 | ObjectOutput out = null;
24 | try {
25 | out = new ObjectOutputStream(bos);
26 | out.writeObject(this);
27 | byte[] yourBytes = bos.toByteArray();
28 | return yourBytes;
29 | } finally {
30 | try {
31 | if (out != null) {
32 | out.close();
33 | }
34 | } catch (IOException ex) {
35 | // ignore close exception
36 | }
37 | try {
38 | bos.close();
39 | } catch (IOException ex) {
40 | // ignore close exception
41 | }
42 | }
43 | }
44 |
45 | public static TestSample fromBytes(byte[] bytes) throws ClassNotFoundException, IOException {
46 | ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
47 | ObjectInput in = null;
48 | try {
49 | in = new ObjectInputStream(bis);
50 | Object o = in.readObject();
51 | return (TestSample) o;
52 | } finally {
53 | try {
54 | bis.close();
55 | } catch (IOException ex) {
56 | // ignore close exception
57 | }
58 | try {
59 | if (in != null) {
60 | in.close();
61 | }
62 | } catch (IOException ex) {
63 | // ignore close exception
64 | }
65 | }
66 | }
67 |
68 | enum BuySell {
69 | Buy, Sell
70 | }
71 |
72 | public static String users(StringBuilder user, int i) {
73 | user.setLength(0);
74 | user.append("user:");
75 | user.append(i);
76 | return user.toString();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/bigcache/utils/TestUtil.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.bigcache.utils;
2 |
3 | import java.io.*;
4 | import java.text.DecimalFormat;
5 | import java.text.NumberFormat;
6 | import java.util.Random;
7 |
8 | /**
9 | * Created on Jul, 2020 by @author bobo
10 | */
11 | public class TestUtil {
12 | static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
13 | static Random rnd = new Random();
14 |
15 | private static final NumberFormat MEM_FMT = new DecimalFormat("##,###.##");
16 |
17 | public static String randomString(int len )
18 | {
19 | StringBuilder sb = new StringBuilder( len );
20 | for( int i = 0; i < len; i++ )
21 | sb.append( AB.charAt( rnd.nextInt(AB.length()) ) );
22 | return sb.toString();
23 | }
24 |
25 | public static void sleepQuietly(long duration) {
26 | try {
27 | Thread.sleep(duration);
28 | } catch (InterruptedException e) {
29 | // ignore
30 | }
31 | }
32 |
33 | public static final String TEST_BASE_DIR = "/Users/william/test/bigcache_test/";
34 |
35 | public static String kbString(long memBytes) {
36 | return MEM_FMT.format(memBytes / 1024) + " kb";
37 | }
38 |
39 | public static String printMemoryFootprint() {
40 | Runtime run = Runtime.getRuntime();
41 | String memoryInfo = "Memory - free: " + kbString(run.freeMemory()) + " - max:" + kbString(run.maxMemory()) + "- total:" + kbString(run.totalMemory());
42 | return memoryInfo;
43 | }
44 |
45 | public static byte[] getBytes(Object o) {
46 | if (o instanceof String) {
47 | return ((String)o).getBytes();
48 | } else if (o instanceof byte[]) {
49 | return (byte[])o;
50 | } else if (o instanceof Serializable) {
51 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
52 | ObjectOutput out = null;
53 | try {
54 | out = new ObjectOutputStream(bos);
55 | out.writeObject(o);
56 | byte[] bytes = bos.toByteArray();
57 | return bytes;
58 | } catch(Exception e) {
59 | return null;
60 | } finally {
61 | try {
62 | if (out != null) {
63 | out.close();
64 | }
65 | } catch (IOException ex) {
66 | // ignore close exception
67 | }
68 | try {
69 | bos.close();
70 | } catch (IOException ex) {
71 | // ignore close exception
72 | }
73 | }
74 | }
75 | throw new RuntimeException("Fail to convert object to bytes");
76 | }
77 |
78 | public static String convertToString(byte[] bytes){
79 | return new String(bytes);
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/lrucache/v1/LruCacheV1Test.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v1;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class LruCacheV1Test {
10 |
11 | @Test
12 | public void test1() {
13 | LruCacheV1 cache = new LruCacheV1<>(5);
14 | cache.put(1, 1);
15 | cache.put(2, 2);
16 | cache.put(3, 3);
17 | cache.put(4, 4);
18 | cache.put(5, 5);
19 |
20 | Assert.assertTrue(cache.get(1) == 1 && cache.get(2) == 2 && cache.get(4) == 4);
21 | }
22 |
23 | @Test
24 | public void test2() {
25 | LruCacheV1 cache = new LruCacheV1<>(5);
26 | cache.put(1, 1);
27 | cache.put(2, 2);
28 | cache.put(3, 3);
29 | cache.put(4, 4);
30 | cache.put(5, 5);
31 |
32 | cache.put(6, 6);
33 |
34 | Assert.assertEquals(cache.size(), 5);
35 | }
36 |
37 | @Test
38 | public void test3() {
39 | LruCacheV1 cache = new LruCacheV1<>(5);
40 | cache.put(1, 1);
41 | cache.put(2, 2);
42 | cache.put(3, 3);
43 | cache.put(4, 4);
44 | cache.put(5, 5);
45 |
46 | cache.put(6, 6);
47 |
48 | cache.put(7, 7);
49 |
50 | Assert.assertTrue(cache.get(4) == 4 && cache.get(6) == 6 && cache.get(7) == 7);
51 | }
52 |
53 | @Test
54 | public void test4() {
55 | LruCacheV1 cache = new LruCacheV1<>(5);
56 | cache.put(1, 1);
57 | cache.put(2, 2);
58 | cache.put(3, 3);
59 | cache.put(4, 4);
60 | cache.put(5, 5);
61 |
62 | cache.put(6, 6);
63 | cache.put(7, 7);
64 |
65 | Assert.assertTrue(cache.get(1) == null);
66 | }
67 |
68 | @Test
69 | public void test5() {
70 | LruCacheV1 cache = new LruCacheV1<>(3);
71 | cache.put(1, 1);
72 | cache.put(2, 2);
73 | cache.put(1, 1);
74 | cache.put(3, 3);
75 | cache.put(4, 4);
76 |
77 | Assert.assertTrue(cache.get(1) == 1);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/lrucache/v2/LruCacheV2PerfTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v2;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertTrue;
5 |
6 | import com.spring2go.bigcache.utils.TestUtil;
7 | import org.junit.Test;
8 |
9 | import java.util.Queue;
10 | import java.util.concurrent.BlockingQueue;
11 | import java.util.concurrent.CountDownLatch;
12 | import java.util.concurrent.LinkedBlockingQueue;
13 | import java.util.concurrent.atomic.AtomicInteger;
14 |
15 | /**
16 | * Created on Jul, 2020 by @author bobo
17 | */
18 | public class LruCacheV2PerfTest {
19 | /********************* configurable parameters *********************/
20 | private static final int LOOP = 5;
21 | private static final int ITEM_COUNT = 1000000;
22 | private static final int PRODUCER_COUNT = 4;
23 | private static final int CONSUMER_COUNT = 4;
24 | private static final int STRING_LEN = 16;
25 | /******************************************************************/
26 |
27 | private final AtomicInteger producingItemCount = new AtomicInteger(0);
28 | private final AtomicInteger consumingItemCount = new AtomicInteger(0);
29 | private final BlockingQueue keysInMemoryQueue = new LinkedBlockingQueue();
30 |
31 | private static LruCacheV2 cache;
32 |
33 | @Test
34 | public void testProduceThenConsume() throws InterruptedException {
35 | System.out.println("LruCache performance test begin ...");
36 | for (int i = 0; i < LOOP; i++) {
37 | cache = new LruCacheV2<>(ITEM_COUNT * 2);
38 | System.out.println("[doRunProduceThenConsume] round " + (i + 1) + " of " + LOOP);
39 | this.doRunProduceThenConsume();
40 | producingItemCount.set(0);
41 | consumingItemCount.set(0);
42 | }
43 | System.out.println("[doRunProduceThenConsume] test ends");
44 | }
45 |
46 | @Test
47 | public void testProduceMixedConsume() throws InterruptedException {
48 | for (int i = 0; i < LOOP; i++) {
49 | cache = new LruCacheV2<>(ITEM_COUNT * 2);
50 | System.out.println("[doRunMixed] round " + (i + 1) + " of " + LOOP);
51 | this.doRunProduceMixedConsume();
52 | producingItemCount.set(0);
53 | consumingItemCount.set(0);
54 | }
55 | System.out.println("[doRunMixed] test ends");
56 | }
57 |
58 | private void doRunProduceThenConsume() throws InterruptedException {
59 | CountDownLatch producerLatch = new CountDownLatch(PRODUCER_COUNT);
60 | CountDownLatch consumerLatch = new CountDownLatch(CONSUMER_COUNT);
61 | BlockingQueue producerResults = new LinkedBlockingQueue();
62 | BlockingQueue consumerResults = new LinkedBlockingQueue();
63 | long start = System.nanoTime();
64 | long totalProducingTime = 0;
65 | long totalConsumingTime = 0;
66 |
67 | for (int i = 0; i < PRODUCER_COUNT; i++) {
68 | Producer p = new Producer(producerLatch, producerResults);
69 | p.start();
70 | }
71 |
72 | for (int i = 0; i < PRODUCER_COUNT; i++) {
73 | Result result = producerResults.take();
74 | assertEquals(result.status, Status.SUCCESS);
75 | totalProducingTime += result.duration;
76 | }
77 | long duration = System.nanoTime() - start;
78 |
79 | System.out.println("-----------------------------------------------");
80 | System.out.println("Producing test result:");
81 | System.out.printf("Total test time = %d ns.\n", duration);
82 | System.out.printf("Total item count = %d\n", ITEM_COUNT);
83 | System.out.printf("Producer thread number = %d\n", PRODUCER_COUNT);
84 | System.out.printf("Item message length = %d bytes\n", STRING_LEN);
85 | System.out.printf("Total producing time = %d ns.\n", totalProducingTime);
86 | System.out.printf("Average producint time = %d ns.\n", totalProducingTime / PRODUCER_COUNT);
87 | System.out.println("-----------------------------------------------");
88 |
89 | // the consumer start
90 | start = System.nanoTime();
91 | for (int i = 0; i < CONSUMER_COUNT; i++) {
92 | Consumer c = new Consumer(consumerLatch, consumerResults);
93 | c.start();
94 | }
95 |
96 | for (int i = 0; i < CONSUMER_COUNT; i++) {
97 | Result result = consumerResults.take();
98 | assertEquals(result.status, Status.SUCCESS);
99 | totalConsumingTime += result.duration;
100 | }
101 | duration = System.nanoTime() - start;
102 | assertEquals(producingItemCount.get(), consumingItemCount.get());
103 | assertTrue(keysInMemoryQueue.isEmpty());
104 |
105 | System.out.println("Consuming test result:");
106 | System.out.printf("Total test time = %d ns.\n", duration);
107 | System.out.printf("Total item count = %d\n", ITEM_COUNT);
108 | System.out.printf("Consumer thread number = %d\n", CONSUMER_COUNT);
109 | System.out.printf("Item message length = %d bytes\n", STRING_LEN);
110 | System.out.printf("Total consuming time = %d ns.\n", totalConsumingTime);
111 | System.out.printf("Average consuming time = %d ns.\n", totalConsumingTime / CONSUMER_COUNT);
112 | System.out.println("-----------------------------------------------");
113 | }
114 |
115 | private void doRunProduceMixedConsume() throws InterruptedException {
116 | CountDownLatch allLatch = new CountDownLatch(PRODUCER_COUNT + CONSUMER_COUNT);
117 | BlockingQueue producerResults = new LinkedBlockingQueue();
118 | BlockingQueue consumerResults = new LinkedBlockingQueue();
119 |
120 | long totalProducingTime = 0;
121 | long totalConsumingTime = 0;
122 |
123 | long start = System.nanoTime();
124 | //run testing
125 | for (int i = 0; i < PRODUCER_COUNT; i++) {
126 | Producer p = new Producer(allLatch, producerResults);
127 | p.start();
128 | }
129 |
130 | for (int i = 0; i < CONSUMER_COUNT; i++) {
131 | Consumer c = new Consumer(allLatch, consumerResults);
132 | c.start();
133 | }
134 |
135 | //verify and report
136 | for (int i = 0; i < PRODUCER_COUNT; i++) {
137 | Result result = producerResults.take();
138 | assertEquals(result.status, Status.SUCCESS);
139 | totalProducingTime += result.duration;
140 | }
141 |
142 | for (int i = 0; i < CONSUMER_COUNT; i++) {
143 | Result result = consumerResults.take();
144 | assertEquals(result.status, Status.SUCCESS);
145 | totalConsumingTime += result.duration;
146 | }
147 |
148 | long duration = System.nanoTime() - start;
149 |
150 | assertEquals(producingItemCount.get(), consumingItemCount.get());
151 |
152 | System.out.println("-----------------------------------------------");
153 | System.out.printf("Total test time = %d ns.\n", duration);
154 | System.out.printf("Total item count = %d\n", ITEM_COUNT);
155 | System.out.printf("Producer thread number = %d\n", PRODUCER_COUNT);
156 | System.out.printf("Consumer thread number = %d\n", CONSUMER_COUNT);
157 | System.out.printf("Item message length = %d bytes\n", STRING_LEN);
158 | System.out.printf("Total consuming time = %d ns.\n", totalConsumingTime);
159 | System.out.printf("Average consuming time = %d ns.\n", totalConsumingTime / CONSUMER_COUNT);
160 | System.out.printf("Total producing time = %d ns.\n", totalProducingTime);
161 | System.out.printf("Average producing time = %d ns.\n", totalProducingTime / PRODUCER_COUNT);
162 | System.out.println("-----------------------------------------------");
163 | }
164 |
165 | private class Producer extends Thread {
166 | private final CountDownLatch latch;
167 | private final Queue resultQueue;
168 |
169 | public Producer(CountDownLatch latch, Queue resultQueue) {
170 | this.latch = latch;
171 | this.resultQueue = resultQueue;
172 | }
173 |
174 | public void run() {
175 | Result result = new Result();
176 | try {
177 | latch.countDown();
178 | latch.await();
179 |
180 | long start = System.nanoTime();
181 | while (true) {
182 | int count = producingItemCount.incrementAndGet();
183 | if (count > ITEM_COUNT)
184 | break;
185 |
186 | String string = TestUtil.randomString(STRING_LEN);
187 |
188 | keysInMemoryQueue.offer(string);
189 | cache.put(string, string);
190 | }
191 | result.status = Status.SUCCESS;
192 | result.duration = System.nanoTime() - start;
193 | } catch (Exception e) {
194 | e.printStackTrace();
195 | result.status = Status.ERROR;
196 | }
197 | resultQueue.offer(result);
198 | }
199 | }
200 |
201 | private class Consumer extends Thread {
202 | private final CountDownLatch latch;
203 | private final Queue resultQueue;
204 |
205 | public Consumer(CountDownLatch latch, Queue resultQueue) {
206 | this.latch = latch;
207 | this.resultQueue = resultQueue;
208 | }
209 |
210 | public void run() {
211 | Result result = new Result();
212 | result.status = Status.SUCCESS;
213 | try {
214 | latch.countDown();
215 | latch.await();
216 |
217 | long start = System.nanoTime();
218 | while (true) {
219 | int count = consumingItemCount.getAndIncrement();
220 | if (count >= ITEM_COUNT)
221 | break;
222 |
223 | String key = keysInMemoryQueue.take();
224 | if (key != null && !key.isEmpty()) {
225 | String value = cache.get(key);
226 | // wait a moment for k/v to be put in the Cache
227 | // may cause dead lock
228 | while (value == null) {
229 | value = cache.get(key);
230 | }
231 | if (!key.equals(value)) {
232 | result.status = Status.ERROR;
233 | }
234 | }
235 | }
236 | result.duration = System.nanoTime() - start;
237 | } catch (Exception e) {
238 | e.printStackTrace();
239 | result.status = Status.ERROR;
240 | }
241 | resultQueue.offer(result);
242 | }
243 | }
244 |
245 | private static enum Status {
246 | ERROR,
247 | SUCCESS
248 | }
249 |
250 | private static class Result {
251 | Status status;
252 | long duration;
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/lrucache/v2/LruCacheV2Test.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v2;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class LruCacheV2Test {
10 | @Test
11 | public void test1() {
12 | LruCacheV2 cache = new LruCacheV2<>(5);
13 | cache.put(1, 1);
14 | cache.put(2, 2);
15 | cache.put(3, 3);
16 | cache.put(4, 4);
17 | cache.put(5, 5);
18 |
19 | Assert.assertTrue(cache.get(1) == 1 && cache.get(2) == 2 && cache.get(4) == 4);
20 | }
21 |
22 | @Test
23 | public void test2() {
24 | LruCacheV2 cache = new LruCacheV2<>(5);
25 | cache.put(1, 1);
26 | cache.put(2, 2);
27 | cache.put(3, 3);
28 | cache.put(4, 4);
29 | cache.put(5, 5);
30 |
31 | cache.put(6, 6);
32 |
33 | Assert.assertEquals(cache.size(), 5);
34 | }
35 |
36 | @Test
37 | public void test3() {
38 | LruCacheV2 cache = new LruCacheV2<>(5);
39 | cache.put(1, 1);
40 | cache.put(2, 2);
41 | cache.put(3, 3);
42 | cache.put(4, 4);
43 | cache.put(5, 5);
44 |
45 | cache.put(6, 6);
46 |
47 | cache.put(7, 7);
48 |
49 | Assert.assertTrue(cache.get(4) == 4 && cache.get(6) == 6 && cache.get(7) == 7);
50 | }
51 |
52 | @Test
53 | public void test4() {
54 | LruCacheV2 cache = new LruCacheV2<>(5);
55 | cache.put(1, 1);
56 | cache.put(2, 2);
57 | cache.put(3, 3);
58 | cache.put(4, 4);
59 | cache.put(5, 5);
60 |
61 | cache.put(6, 6);
62 | cache.put(7, 7);
63 |
64 | Assert.assertTrue(cache.get(1) == null);
65 | }
66 |
67 | @Test
68 | public void test5() {
69 | LruCacheV2 cache = new LruCacheV2<>(3);
70 | cache.put(1, 1);
71 | cache.put(2, 2);
72 | cache.put(1, 1);
73 | cache.put(3, 3);
74 | cache.put(4, 4);
75 |
76 | Assert.assertTrue(cache.get(1) == 1);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/lrucache/v3/LruCacheV3Test.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.lrucache.v3;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | /**
7 | * Created on Jul, 2020 by @author bobo
8 | */
9 | public class LruCacheV3Test {
10 | @Test
11 | public void test1() {
12 | LruCacheV3 cache = new LruCacheV3<>(1,5);
13 | cache.put(1, 1);
14 | cache.put(2, 2);
15 | cache.put(3, 3);
16 | cache.put(4, 4);
17 | cache.put(5, 5);
18 |
19 | Assert.assertTrue(cache.get(1) == 1 && cache.get(2) == 2 && cache.get(4) == 4);
20 | }
21 |
22 | @Test
23 | public void test2() {
24 | LruCacheV3 cache = new LruCacheV3<>(1,5);
25 | cache.put(1, 1);
26 | cache.put(2, 2);
27 | cache.put(3, 3);
28 | cache.put(4, 4);
29 | cache.put(5, 5);
30 |
31 | cache.put(6, 6);
32 |
33 | Assert.assertEquals(cache.size(), 5);
34 | }
35 |
36 | @Test
37 | public void test3() {
38 | LruCacheV3 cache = new LruCacheV3<>(1,5);
39 | cache.put(1, 1);
40 | cache.put(2, 2);
41 | cache.put(3, 3);
42 | cache.put(4, 4);
43 | cache.put(5, 5);
44 |
45 | cache.put(6, 6);
46 |
47 | cache.put(7, 7);
48 |
49 | Assert.assertTrue(cache.get(4) == 4 && cache.get(6) == 6 && cache.get(7) == 7);
50 | }
51 |
52 | @Test
53 | public void test4() {
54 | LruCacheV3 cache = new LruCacheV3<>(1, 5);
55 | cache.put(1, 1);
56 | cache.put(2, 2);
57 | cache.put(3, 3);
58 | cache.put(4, 4);
59 | cache.put(5, 5);
60 |
61 | cache.put(6, 6);
62 | cache.put(7, 7);
63 |
64 | Assert.assertTrue(cache.get(1) == null);
65 | }
66 |
67 | @Test
68 | public void test5() {
69 | LruCacheV3 cache = new LruCacheV3<>(1,3);
70 | cache.put(1, 1);
71 | cache.put(2, 2);
72 | cache.put(1, 1);
73 | cache.put(3, 3);
74 | cache.put(4, 4);
75 |
76 | Assert.assertTrue(cache.get(1) == 1);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/okcache/CacheImplTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | /**
11 | * Created on Jul, 2020 by @author bobo
12 | */
13 | public class CacheImplTest {
14 | public Cache cache5() {
15 | Cache cache = CacheBuilder.newBuilder(Integer.class, String.class)
16 | .expireAfterAccess(1000, TimeUnit.MILLISECONDS)
17 | .expireAfterWrite(1500, TimeUnit.MILLISECONDS)
18 | .build();
19 | cache.put(1, "A");
20 | cache.put(2, "B");
21 | cache.put(3, "C");
22 | cache.put(4, "D");
23 | cache.put(5, "E");
24 | return cache;
25 | }
26 |
27 | public Cache cache5b() {
28 | Cache cache = CacheBuilder.newBuilder(Integer.class, String.class)
29 | .expireAfterWrite(1500, TimeUnit.MILLISECONDS)
30 | .build();
31 | cache.put(1, "A");
32 | cache.put(2, "B");
33 | cache.put(3, "C");
34 | cache.put(4, "D");
35 | cache.put(5, "E");
36 | return cache;
37 | }
38 |
39 | @Test
40 | public void testGet() throws InterruptedException {
41 | Cache cache = cache5();
42 | Assert.assertEquals(cache.get(1), "A");
43 | Assert.assertEquals(cache.get(2), "B");
44 | Assert.assertEquals(cache.get(3), "C");
45 | Assert.assertEquals(cache.get(4), "D");
46 | Assert.assertEquals(cache.get(5), "E");
47 | Thread.sleep(2000);
48 | Assert.assertEquals(cache.get(1), null);
49 | Assert.assertEquals(cache.get(2), null);
50 | Assert.assertEquals(cache.get(3), null);
51 | Assert.assertEquals(cache.get(4), null);
52 | Assert.assertEquals(cache.get(5), null);
53 | }
54 |
55 | @Test
56 | public void testGet2() {
57 | Cache cache = cache5();
58 | Assert.assertEquals(cache.get(1), "A");
59 | cache.put(1, "W");
60 | Assert.assertEquals(cache.get(1), "W");
61 | }
62 |
63 | @Test
64 | public void testGet3() throws InterruptedException {
65 | Cache cache = cache5();
66 | Assert.assertEquals(cache.get(1), "A");
67 | Assert.assertEquals(cache.get(2), "B");
68 | Thread.sleep(800);
69 | Assert.assertEquals(cache.get(1), "A");
70 | Assert.assertEquals(cache.get(2, Cache.UpdateTimestamp.Write), "B");
71 | Thread.sleep(800);
72 | Assert.assertEquals(cache.get(1), null);
73 | Assert.assertEquals(cache.get(2), "B");
74 | }
75 |
76 | @Test
77 | public void testGet4() throws InterruptedException {
78 | Cache cache = cache5b();
79 | Assert.assertEquals(cache.get(1), "A");
80 | Assert.assertEquals(cache.get(2), "B");
81 | Thread.sleep(1000);
82 | Assert.assertEquals(cache.get(1), "A");
83 | Assert.assertEquals(cache.get(2, Cache.UpdateTimestamp.Write), "B");
84 | Thread.sleep(1000);
85 | Assert.assertEquals(cache.get(1), null);
86 | Assert.assertEquals(cache.get(2), "B");
87 | Thread.sleep(1000);
88 | Assert.assertEquals(cache.get(1), null);
89 | Assert.assertEquals(cache.get(2), null);
90 | }
91 |
92 | @Test
93 | public void testPut() {
94 | Cache cache = cache5();
95 | cache.put(6, "F");
96 | Assert.assertEquals(cache.get(6), "F");
97 | }
98 |
99 | @Test
100 | public void testPut2() {
101 | Cache cache = cache5();
102 | String old = cache.put(1, "W");
103 | Assert.assertEquals(old, "A");
104 | Assert.assertEquals(cache.get(1), "W");
105 | }
106 |
107 | @Test
108 | public void testPut3() throws InterruptedException {
109 | Cache cache = cache5();
110 | cache.put(7, "7A", ExpirationPolicy.afterWrite(3000));
111 | cache.put(8,"8A",ExpirationPolicy.afterCreate(5000));
112 | cache.put(9,"9A",ExpirationPolicy.never());
113 |
114 | Assert.assertEquals(cache.get(1), "A");
115 | Assert.assertEquals(cache.get(7), "7A");
116 | Assert.assertEquals(cache.get(8), "8A");
117 | Assert.assertEquals(cache.get(9), "9A");
118 |
119 | Thread.sleep(1001);
120 |
121 | Assert.assertEquals(cache.get(1), null);
122 | Assert.assertEquals(cache.get(7), "7A");
123 | Assert.assertEquals(cache.get(8), "8A");
124 | Assert.assertEquals(cache.get(9), "9A");
125 |
126 | Thread.sleep(2000);
127 |
128 | Assert.assertEquals(cache.get(1), null);
129 | Assert.assertEquals(cache.get(7), null);
130 | Assert.assertEquals(cache.get(8), "8A");
131 | Assert.assertEquals(cache.get(9), "9A");
132 |
133 | Thread.sleep(2000);
134 |
135 | Assert.assertEquals(cache.get(1), null);
136 | Assert.assertEquals(cache.get(7), null);
137 | Assert.assertEquals(cache.get(8), null);
138 | Assert.assertEquals(cache.get(9), "9A");
139 | }
140 |
141 | @Test
142 | public void testPutIfAbsent() throws InterruptedException {
143 | Cache cache = cache5();
144 | String current = cache.putIfAbsent(1, "W");
145 | Assert.assertEquals(current, "A");
146 | Assert.assertEquals(cache.get(1), "A");
147 |
148 | current = cache.putIfAbsent(6, "F");
149 | Assert.assertEquals(current, null);
150 | Assert.assertEquals(cache.get(6), "F");
151 |
152 | Thread.sleep(2000);
153 | current = cache.putIfAbsent(1, "W");
154 | Assert.assertEquals(current, null);
155 | Assert.assertEquals(cache.get(1), "W");
156 |
157 | }
158 |
159 | @Test
160 | public void testPutAll() {
161 | Map map = new HashMap();
162 | map.put(11, "AA");
163 | map.put(12, "AB");
164 | map.put(13, "AC");
165 | map.put(14, "AD");
166 | map.put(15, "AE");
167 |
168 | Cache cache = cache5();
169 | cache.putAll(map);
170 |
171 | Assert.assertEquals(cache.get(11), "AA");
172 | Assert.assertEquals(cache.get(12), "AB");
173 | Assert.assertEquals(cache.get(13), "AC");
174 | Assert.assertEquals(cache.get(14), "AD");
175 | Assert.assertEquals(cache.get(15), "AE");
176 | }
177 |
178 | @Test
179 | public void testReplace() throws InterruptedException {
180 | Cache cache = cache5();
181 | String old = cache.replace(6, "F");
182 | Assert.assertEquals(old, null);
183 | Assert.assertEquals(cache.get(6), null);
184 |
185 | old = cache.replace(1, "W");
186 | Assert.assertEquals(old, "A");
187 | Assert.assertEquals(cache.get(1), "W");
188 |
189 | Thread.sleep(2000);
190 |
191 | old = cache.replace(1, "A");
192 | Assert.assertEquals(old, null);
193 | Assert.assertEquals(cache.get(1), null);
194 | }
195 |
196 | @Test
197 | public void testReplace2() throws InterruptedException {
198 | Cache cache = cache5();
199 | boolean success = cache.replace(6, "E", "F");
200 | Assert.assertEquals(success, false);
201 | Assert.assertEquals(cache.get(6), null);
202 |
203 | success = cache.replace(1, "B", "W");
204 | Assert.assertEquals(success, false);
205 | Assert.assertEquals(cache.get(1), "A");
206 |
207 | success = cache.replace(1, "A", "W");
208 | Assert.assertEquals(success, true);
209 | Assert.assertEquals(cache.get(1), "W");
210 |
211 | Thread.sleep(2000);
212 |
213 | success = cache.replace(1, "W", "A");
214 | Assert.assertEquals(success, false);
215 | Assert.assertEquals(cache.get(1), null);
216 | }
217 |
218 | @Test
219 | public void testRemove() throws InterruptedException {
220 | Cache cache = cache5();
221 | String old = cache.remove(1);
222 | Assert.assertEquals(old, "A");
223 | Assert.assertEquals(cache.get(1), null);
224 |
225 | old = cache.remove(6);
226 | Assert.assertEquals(old, null);
227 | Assert.assertEquals(cache.get(6), null);
228 |
229 | Thread.sleep(2000);
230 |
231 | old = cache.remove(2);
232 | Assert.assertEquals(old, null);
233 | Assert.assertEquals(cache.get(2), null);
234 | }
235 |
236 | @Test
237 | public void testRemove2() throws InterruptedException {
238 | Cache cache = cache5();
239 | boolean success = cache.remove(6, "E");
240 | Assert.assertEquals(success, false);
241 | Assert.assertEquals(cache.get(6), null);
242 |
243 | success = cache.remove(1, "B");
244 | Assert.assertEquals(success, false);
245 | Assert.assertEquals(cache.get(1), "A");
246 |
247 | success = cache.remove(1, "A");
248 | Assert.assertEquals(success, true);
249 | Assert.assertEquals(cache.get(1), null);
250 |
251 | Thread.sleep(2000);
252 |
253 | success = cache.remove(2, "B");
254 | Assert.assertEquals(success, false);
255 | Assert.assertEquals(cache.get(2), null);
256 | }
257 |
258 | @Test
259 | public void testContains() throws InterruptedException {
260 | Cache cache = cache5();
261 | Assert.assertEquals(cache.contains(1), true);
262 | Assert.assertEquals(cache.contains(6), false);
263 |
264 | Thread.sleep(2000);
265 |
266 | Assert.assertEquals(cache.contains(1), false);
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/okcache/CachePerfGetsTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.Serializable;
6 | import java.util.Map;
7 | import java.util.Random;
8 | import java.util.concurrent.ConcurrentHashMap;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 |
12 | /**
13 | * Created on Jul, 2020 by @author bobo
14 | */
15 | public class CachePerfGetsTest {
16 | @Test
17 | public void stressGet(){
18 | final Cache cache = CacheBuilder.newBuilder(Serializable.class,Serializable.class)
19 | .expireAfterWrite(1000, TimeUnit.MINUTES)
20 | .maximumSize(200000)
21 | .concurrencyLevel(25)
22 | .build();
23 |
24 | final int eCount = 20000;
25 | final Serializable[] ks = new Serializable[eCount];
26 | for (int i = 0; i < eCount; i++) {
27 | cache.put("k" + i, "v" + i);
28 | ks[i] = "k" + i;
29 | }
30 |
31 |
32 | final AtomicInteger totalCount = new AtomicInteger();
33 |
34 | int tCount = 25;
35 | final int time = 1000*60;
36 | Thread[] ts = new Thread[tCount];
37 | for (int i = 0; i < tCount; i++) {
38 | ts[i] = new Thread(){
39 | int count = 0;
40 | @Override
41 | public void run() {
42 | Random random = new Random();
43 | long start = System.currentTimeMillis();
44 | while(true){
45 | int i = random.nextInt(eCount);
46 | cache.get(ks[i]);
47 | count++;
48 | if(System.currentTimeMillis() - start >= time) break;
49 | }
50 | totalCount.addAndGet(count);
51 | }
52 | };
53 | }
54 |
55 | for (Thread t : ts) {
56 | t.start();
57 | }
58 |
59 | for (Thread t : ts) {
60 | try {
61 | t.join();
62 | } catch (InterruptedException e) {
63 | e.printStackTrace();
64 | }
65 | }
66 |
67 | System.out.println("Total Count:" + totalCount.get());
68 | System.out.println("Ops/Sec:" + (totalCount.get()/(time/1000)));
69 |
70 | }
71 |
72 | @Test
73 | public void stressConcurrentHashMap(){
74 | final Map cache = new ConcurrentHashMap(1000,0.7f, 4);
75 |
76 | final int eCount = 20000;
77 | final Serializable[] ks = new Serializable[eCount];
78 | for (int i = 0; i < eCount; i++) {
79 | cache.put("k" + i, "v" + i);
80 | ks[i] = "k" + i;
81 | }
82 |
83 |
84 | final AtomicInteger totalCount = new AtomicInteger();
85 |
86 | int tCount = 25;
87 | final int time = 1000*60;
88 | Thread[] ts = new Thread[tCount];
89 | for (int i = 0; i < tCount; i++) {
90 | ts[i] = new Thread(){
91 | int count = 0;
92 | @Override
93 | public void run() {
94 | Random random = new Random();
95 | long start = System.currentTimeMillis();
96 | while(true){
97 | int i = random.nextInt(eCount);
98 | cache.put(ks[i], "v" + i);
99 | //cache.get(ks[i]);
100 | count++;
101 | if(System.currentTimeMillis() - start >= time) break;
102 | }
103 | totalCount.addAndGet(count);
104 | }
105 | };
106 | }
107 |
108 | for (Thread t : ts) {
109 | t.start();
110 | }
111 |
112 | for (Thread t : ts) {
113 | try {
114 | t.join();
115 | } catch (InterruptedException e) {
116 | e.printStackTrace();
117 | }
118 | }
119 |
120 | System.out.println("Total Count:" + totalCount.get());
121 | System.out.println("Ops/Sec:" + (totalCount.get()/(time/1000)));
122 |
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/test/java/com/spring2go/okcache/CachePerfTest.java:
--------------------------------------------------------------------------------
1 | package com.spring2go.okcache;
2 |
3 | import com.spring2go.bigcache.BigCache;
4 | import com.spring2go.bigcache.CacheConfig;
5 | import com.spring2go.bigcache.utils.TestUtil;
6 | import com.spring2go.okcache.store.BigCacheStore;
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 |
10 | import java.io.IOException;
11 | import java.util.concurrent.*;
12 |
13 | /**
14 | * Created on Jul, 2020 by @author bobo
15 | */
16 | public class CachePerfTest {
17 | public Cache cache2000() throws IOException {
18 | CacheConfig config = new CacheConfig();
19 | config.setStorageMode(CacheConfig.StorageMode.OffHeapPlusFile)
20 | .setCapacityPerBlock(16 * 1024 * 1024)
21 | .setMaxOffHeapMemorySize(16 * 1024 * 1024)
22 | .setMergeInterval(2 * 1000)
23 | .setPurgeInterval(2 * 1000);
24 | BigCache bigcache = new BigCache(TestUtil.TEST_BASE_DIR, config);
25 | BigCacheStore evictStore = new BigCacheStore(bigcache);
26 | Cache cache = CacheBuilder.newBuilder(Integer.class, String.class)
27 | .expireAfterAccess(1000*30, TimeUnit.MILLISECONDS)
28 | .maximumSize(2000)
29 | .addEvictStore(evictStore)
30 | .build();
31 |
32 | for (int i = 1; i <= 200; i++) {
33 | cache.put(i, "V" + i);
34 | }
35 | return cache;
36 | }
37 |
38 | @Test
39 | public void testPutAndGet() throws IOException, ExecutionException, InterruptedException {
40 | int count = 300000;
41 | int threadCount = 8;
42 |
43 | final Cache cache = cache2000();
44 | ExecutorService executor = Executors.newFixedThreadPool(threadCount);
45 | Future[] futures = new Future[count];
46 |
47 | long start = System.nanoTime();
48 | for (int i = 0; i < count; i++) {
49 | final int finalI = i;
50 | futures[i] = executor.submit(new Callable