mirror of
https://github.com/LamGC/ContentGrabbingJi.git
synced 2025-04-29 22:27:33 +00:00
[Add] 添加基于 Hashtable 的本地缓存存储容器;
[Add] HashCacheStore 添加 CacheStore 的抽象实现; [Add] HashSingleCacheStore 添加 SingleCacheStore 的具体实现; [Add] HashSingleCacheStoreTest 增加完整测试项(涵盖了 HashCacheStore);
This commit is contained in:
parent
9f08ca0eba
commit
7461376141
145
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/HashCacheStore.java
vendored
Normal file
145
ContentGrabbingJi-CacheStore-local/src/main/java/net/lamgc/cgj/bot/cache/local/HashCacheStore.java
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2020 LamGC
|
||||
*
|
||||
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.lamgc.cgj.bot.cache.local;
|
||||
|
||||
import net.lamgc.cgj.bot.cache.CacheStore;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 基于 {@link Hashtable} 的缓存存储容器.
|
||||
* @param <V> 值类型.
|
||||
* @author LamGC
|
||||
* @see net.lamgc.cgj.bot.cache.CacheStore
|
||||
* @see Hashtable
|
||||
*/
|
||||
public abstract class HashCacheStore<V> implements CacheStore<V> {
|
||||
|
||||
private final Map<String, CacheItem<V>> cacheMap = new Hashtable<>();
|
||||
|
||||
/**
|
||||
* 获取内部 Map 对象.
|
||||
* 仅供其他子类使用.
|
||||
* @return 返回存储缓存项的 Map.
|
||||
*/
|
||||
protected Map<String, CacheItem<V>> getCacheMap() {
|
||||
return cacheMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTimeToLive(String key, long ttl) {
|
||||
if (!exists(key)) {
|
||||
return false;
|
||||
}
|
||||
CacheItem<V> item = cacheMap.get(key);
|
||||
item.setExpireDate(ttl < 0 ? null : new Date(System.currentTimeMillis() + ttl));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeToLive(String key) {
|
||||
if (!exists(key)) {
|
||||
return -1;
|
||||
}
|
||||
CacheItem<V> item = cacheMap.get(key);
|
||||
Date expireDate = item.getExpireDate();
|
||||
if (expireDate != null) {
|
||||
return expireDate.getTime() - System.currentTimeMillis();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
return cacheMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear() {
|
||||
cacheMap.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String key) {
|
||||
if (!cacheMap.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
CacheItem<V> item = cacheMap.get(key);
|
||||
// 在检查其过期情况后根据情况进行清理, 减轻主动清理机制的负担.
|
||||
if (item.isExpire(new Date())) {
|
||||
remove(key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String key) {
|
||||
// 根据 Collection 说明, 删除时 key 存在映射就会返回, 只要返回 null 就代表没有.
|
||||
return cacheMap.remove(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return Collections.unmodifiableSet(cacheMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存项.
|
||||
* @author LamGC
|
||||
*/
|
||||
protected final static class CacheItem<V> {
|
||||
private final V value;
|
||||
private Date expireDate;
|
||||
|
||||
CacheItem(V value) {
|
||||
this(value, null);
|
||||
}
|
||||
|
||||
CacheItem(V value, Date expireDate) {
|
||||
this.value = value;
|
||||
this.expireDate = expireDate;
|
||||
}
|
||||
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setExpireDate(Date expireDate) {
|
||||
this.expireDate = expireDate;
|
||||
}
|
||||
|
||||
public Date getExpireDate() {
|
||||
return expireDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存项是否过期.
|
||||
* @param date 当前时间.
|
||||
* @return 如果已设置过期时间且早于提供的Date, 则该缓存项过期, 返回 true.
|
||||
* @throws NullPointerException 当 date 传入 null 时抛出.
|
||||
*/
|
||||
public boolean isExpire(Date date) {
|
||||
Date expireDate = getExpireDate();
|
||||
return expireDate != null && expireDate.before(Objects.requireNonNull(date));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2020 LamGC
|
||||
*
|
||||
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.lamgc.cgj.bot.cache.local;
|
||||
|
||||
import net.lamgc.cgj.bot.cache.SingleCacheStore;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于 {@link java.util.Hashtable} 的 Map 缓存存储容器.
|
||||
* @param <V> 值类型.
|
||||
* @author LamGC
|
||||
*/
|
||||
public class HashSingleCacheStore<V> extends HashCacheStore<V> implements SingleCacheStore<V> {
|
||||
|
||||
@Override
|
||||
public boolean set(String key, V value) {
|
||||
getCacheMap().put(Objects.requireNonNull(key), new CacheItem<>(Objects.requireNonNull(value)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setIfNotExist(String key, V value) {
|
||||
if (exists(key)) {
|
||||
return false;
|
||||
}
|
||||
return set(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(String key) {
|
||||
if (!exists(key)) {
|
||||
return null;
|
||||
}
|
||||
return getCacheMap().get(key).getValue();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2020 LamGC
|
||||
*
|
||||
* ContentGrabbingJi is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* ContentGrabbingJi is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.lamgc.cgj.bot.cache.local;
|
||||
|
||||
import net.lamgc.cgj.bot.cache.SingleCacheStore;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @see HashSingleCacheStore
|
||||
* @see HashCacheStore
|
||||
*/
|
||||
public class HashSingleCacheStoreTest {
|
||||
|
||||
@Test
|
||||
public void nullThrowTest() {
|
||||
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set(null, "testValue"));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.set("testKey", null));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist(null, "testValue"));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setIfNotExist("testKey", null));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.exists(null));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.getTimeToLive(null));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.setTimeToLive(null, 0));
|
||||
Assert.assertThrows(NullPointerException.class, () -> cacheStore.remove(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAndGetTest() {
|
||||
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
final String key = "test01";
|
||||
final String value = "testValue";
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertEquals(value, cacheStore.get("test01"));
|
||||
Assert.assertTrue("Remove operation failed!", cacheStore.remove(key));
|
||||
Assert.assertNull("Set operation failed!", cacheStore.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setIfNotExistTest() {
|
||||
SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
final String key = "test01";
|
||||
final String value = "testValue";
|
||||
final String value2 = "testValue02";
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertFalse(cacheStore.setIfNotExist(key, value2));
|
||||
Assert.assertEquals(value, cacheStore.get(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expireTest() throws InterruptedException {
|
||||
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
final String key = "test01";
|
||||
final String value = "testValue";
|
||||
|
||||
// Cache
|
||||
Assert.assertFalse(cacheStore.setTimeToLive(key, 300));
|
||||
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
|
||||
|
||||
// TTL 到期被动检查测试: 使用 exists 经 expire 检查失败后返回 false.
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
|
||||
Assert.assertNotEquals(-1, cacheStore.getTimeToLive(key));
|
||||
Thread.sleep(300);
|
||||
Assert.assertFalse(cacheStore.exists(key));
|
||||
|
||||
// 取消 TTL 测试
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
|
||||
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, -1));
|
||||
Thread.sleep(300);
|
||||
Assert.assertTrue(cacheStore.exists(key));
|
||||
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeTest() {
|
||||
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
final String key = "test01";
|
||||
final String value = "testValue";
|
||||
|
||||
// 删除不存在Cache测试
|
||||
Assert.assertFalse(cacheStore.remove(key));
|
||||
// 删除存在的Cache测试
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertTrue(cacheStore.remove(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearTest() {
|
||||
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
final String key = "test01";
|
||||
final String value = "testValue";
|
||||
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
|
||||
Assert.assertTrue("before-exists operation failed!", cacheStore.exists(key));
|
||||
Assert.assertTrue("Clear operation failed!", cacheStore.clear());
|
||||
Assert.assertFalse("after-exists operation failed!", cacheStore.exists(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sizeAndKeySetTest() {
|
||||
Map<String, String> expectedMap = new HashMap<>();
|
||||
expectedMap.put("test01", "testValue01");
|
||||
expectedMap.put("test02", "testValue02");
|
||||
expectedMap.put("test03", "testValue03");
|
||||
expectedMap.put("test04", "testValue04");
|
||||
expectedMap.put("test05", "testValue05");
|
||||
expectedMap.put("test06", "testValue06");
|
||||
|
||||
final SingleCacheStore<String> cacheStore = new HashSingleCacheStore<>();
|
||||
expectedMap.forEach(cacheStore::set);
|
||||
Assert.assertEquals(expectedMap.size(), cacheStore.size());
|
||||
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.keySet()));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user