-
Notifications
You must be signed in to change notification settings - Fork 477
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
YangSen-qn
committed
Nov 2, 2023
1 parent
e95a91f
commit 18ba83b
Showing
5 changed files
with
276 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package com.qiniu.util; | ||
|
||
import com.qiniu.common.Constants; | ||
import com.qiniu.storage.persistent.FileRecorder; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
|
||
/** | ||
* 包含内存缓存和磁盘缓存 | ||
* 磁盘缓存会被缓存在一个文件中 | ||
*/ | ||
public class Cache<T> { | ||
|
||
// 当 cache 修改数量到达这个值时,就会 flush,默认是 1 | ||
private final int flushCount; | ||
|
||
// 缓存被持久化为一个文件,此文件的文件名为 version,version 默认为:v1 | ||
private final String version; | ||
|
||
// 存储对象的类型 | ||
private final Class<T> objectClass; | ||
|
||
// 内部 | ||
private boolean isFlushing = false; | ||
private int needFlushCount = 0; | ||
|
||
private final ConcurrentHashMap<String, T> memCache = new ConcurrentHashMap<>(); | ||
private final FileRecorder diskCache; | ||
|
||
private Cache(Class<T> objectClass, String cacheDir, int flushCount, String version) { | ||
this.objectClass = objectClass; | ||
this.flushCount = flushCount; | ||
this.version = version; | ||
|
||
FileRecorder fileRecorder = null; | ||
try { | ||
if (objectClass != null && cacheDir != null && !cacheDir.isEmpty()) { | ||
fileRecorder = new FileRecorder(cacheDir + "/" + objectClass.getName()); | ||
} | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
} | ||
this.diskCache = fileRecorder; | ||
|
||
this.load(); | ||
} | ||
|
||
private void load() { | ||
if (this.diskCache == null || objectClass == null) { | ||
return; | ||
} | ||
|
||
byte[] cacheData = this.diskCache.get(this.version); | ||
if (cacheData == null || cacheData.length == 0) { | ||
return; | ||
} | ||
|
||
try { | ||
HashMap<String, Object> cacheJson = Json.decode(new String(cacheData), HashMap.class); | ||
for (String key : cacheJson.keySet()) { | ||
try { | ||
Object jsonMap = cacheJson.get(key); | ||
String jsonString = Json.encode(jsonMap); | ||
T object = Json.decode(jsonString, this.objectClass); | ||
this.memCache.put(key, object); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
break; | ||
} | ||
} | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
this.diskCache.del(this.version); | ||
} | ||
} | ||
|
||
public T cacheForKey(String cacheKey) { | ||
return this.memCache.get(cacheKey); | ||
} | ||
|
||
public void cache(String cacheKey, T object) { | ||
if (StringUtils.isNullOrEmpty(cacheKey) || object == null) { | ||
return; | ||
} | ||
|
||
synchronized (this) { | ||
this.needFlushCount++; | ||
this.memCache.put(cacheKey, object); | ||
|
||
if (this.needFlushCount < this.flushCount) { | ||
return; | ||
} | ||
|
||
this.needFlushCount = 0; | ||
} | ||
|
||
this.flush(); | ||
} | ||
|
||
private void flush() { | ||
if (this.diskCache == null) { | ||
return; | ||
} | ||
|
||
Map<String, T> flushCache = null; | ||
synchronized (this) { | ||
if (this.isFlushing) { | ||
return; | ||
} | ||
|
||
this.isFlushing = true; | ||
flushCache = new HashMap<>(this.memCache); | ||
} | ||
|
||
if (flushCache.isEmpty()) { | ||
return; | ||
} | ||
|
||
String jsonString = Json.encode(flushCache); | ||
if (jsonString == null || jsonString.isEmpty()) { | ||
return; | ||
} | ||
|
||
byte[] cacheData = jsonString.getBytes(); | ||
if (cacheData.length == 0) { | ||
return; | ||
} | ||
|
||
this.diskCache.set(this.version, cacheData); | ||
|
||
synchronized (this) { | ||
isFlushing = false; | ||
} | ||
} | ||
|
||
public void clearMemoryCache() { | ||
this.memCache.clear(); | ||
} | ||
|
||
public static class Builder<T> { | ||
// 当 cache 修改数量到达这个值时,就会 flush,默认是 1 | ||
private int flushCount = 1; | ||
|
||
// 缓存被持久化为一个文件,此文件的文件名为 version,version 默认为:v1 | ||
private String version = "v1"; | ||
|
||
// 存储路径 | ||
private String cacheDir = Constants.CACHE_DIR; | ||
|
||
// 存储对象的类型 | ||
private final Class<T> objectClass; | ||
|
||
public Builder(Class<T> objectClass) { | ||
this.objectClass = objectClass; | ||
} | ||
|
||
public Builder<T> setFlushCount(int flushCount) { | ||
this.flushCount = flushCount; | ||
return this; | ||
} | ||
|
||
public Builder<T> setCacheDir(String cacheDir) { | ||
this.cacheDir = cacheDir; | ||
return this; | ||
} | ||
|
||
public Builder<T> setVersion(String version) { | ||
this.version = version; | ||
return this; | ||
} | ||
|
||
public Cache<T> builder() { | ||
return new Cache<>(this.objectClass, cacheDir, this.flushCount, this.version); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package test.com.qiniu.util; | ||
|
||
import com.qiniu.util.Cache; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
public class CacheTest { | ||
|
||
@Test | ||
public void testCache() { | ||
Info info = new Info(); | ||
info.foo = "foo"; | ||
info.bar = 1; | ||
|
||
String key = "info_key"; | ||
Cache cache = new Cache.Builder(Info.class) | ||
.setVersion("v1") | ||
.setFlushCount(1) | ||
.builder(); | ||
|
||
cache.cache(key, info); | ||
|
||
|
||
// 1. 测试内存缓存 | ||
Info memInfo = (Info) cache.cacheForKey(key); | ||
assertEquals("foo", memInfo.foo); | ||
|
||
// 2. 测试删除内存缓存 | ||
cache.clearMemoryCache(); | ||
memInfo = (Info) cache.cacheForKey(key); | ||
assertEquals(null, memInfo); | ||
|
||
// 3. 测试 load | ||
cache = new Cache.Builder(Info.class) | ||
.setVersion("v1") | ||
.setFlushCount(1) | ||
.builder(); | ||
memInfo = (Info) cache.cacheForKey(key); | ||
assertEquals("foo", memInfo.foo); | ||
} | ||
|
||
static class Info { | ||
String foo; | ||
int bar; | ||
|
||
Info(){ | ||
} | ||
} | ||
} |