mirror of
https://gitee.com/dromara/hutool.git
synced 2026-02-09 09:16:26 +08:00
修复NumberUtil.getBinaryStr方法计算Double等丢失小数问题(pr#1411@Gitee)
This commit is contained in:
@@ -1,576 +0,0 @@
|
||||
package cn.hutool.v7.core.cache.impl;
|
||||
|
||||
import cn.hutool.v7.core.cache.Cache;
|
||||
import cn.hutool.v7.core.cache.smart.CacheStats;
|
||||
import cn.hutool.v7.core.cache.smart.SmartCache;
|
||||
import cn.hutool.v7.core.collection.CollUtil;
|
||||
import cn.hutool.v7.core.collection.iter.CopiedIter;
|
||||
import cn.hutool.v7.core.collection.partition.Partition;
|
||||
import cn.hutool.v7.core.exception.HutoolException;
|
||||
import cn.hutool.v7.core.func.SerSupplier;
|
||||
import cn.hutool.v7.core.map.MapUtil;
|
||||
import cn.hutool.v7.core.text.StrUtil;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 智能缓存实现
|
||||
*
|
||||
* @param <K> 缓存键类型
|
||||
* @param <V> 缓存值类型
|
||||
* @author Nic
|
||||
*/
|
||||
public class SmartCacheImpl<K, V> implements SmartCache<K, V> {
|
||||
|
||||
// 底层缓存
|
||||
private final Cache<K, V> delegate;
|
||||
|
||||
// 配置参数
|
||||
private String name;
|
||||
private final boolean enableStats;
|
||||
private final boolean enableAsyncRefresh;
|
||||
private final int warmUpBatchSize;
|
||||
private final Duration refreshTimeout;
|
||||
private final ExecutorService refreshExecutor;
|
||||
private final Function<K, V> cacheLoader;
|
||||
|
||||
// 统计信息
|
||||
private final CacheStats stats;
|
||||
|
||||
// 锁机制
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private final Map<K, CompletableFuture<V>> pendingRefreshes = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param delegate 底层缓存实现
|
||||
* @param name 缓存名称
|
||||
* @param enableStats 是否启用统计信息
|
||||
* @param enableAsyncRefresh 是否启用异步刷新
|
||||
* @param warmUpBatchSize warmUpBatchSize
|
||||
* @param refreshTimeout 刷新超时时间
|
||||
* @param refreshExecutor 刷新执行器
|
||||
* @param cacheLoader 缓存加载器
|
||||
*/
|
||||
public SmartCacheImpl(
|
||||
final Cache<K, V> delegate,
|
||||
final String name,
|
||||
final boolean enableStats,
|
||||
final boolean enableAsyncRefresh,
|
||||
final int warmUpBatchSize,
|
||||
final Duration refreshTimeout,
|
||||
final ExecutorService refreshExecutor,
|
||||
final Function<K, V> cacheLoader) {
|
||||
|
||||
this.delegate = delegate;
|
||||
this.name = name;
|
||||
this.enableStats = enableStats;
|
||||
this.enableAsyncRefresh = enableAsyncRefresh;
|
||||
this.warmUpBatchSize = Math.max(1, warmUpBatchSize);
|
||||
this.refreshTimeout = refreshTimeout != null ? refreshTimeout : Duration.ofSeconds(30);
|
||||
this.refreshExecutor = refreshExecutor;
|
||||
this.cacheLoader = cacheLoader;
|
||||
this.stats = enableStats ? new CacheStats() : null;
|
||||
}
|
||||
|
||||
// ========== 实现Cache接口方法 ==========
|
||||
|
||||
@Override
|
||||
public void put(final K key, final V object, final long timeout) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
delegate.put(key, object, timeout);
|
||||
if (enableStats) {
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final K key, final V object) {
|
||||
put(key, object, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
V value = delegate.get(key, isUpdateLastAccess);
|
||||
|
||||
if (enableStats) {
|
||||
if (value != null) {
|
||||
stats.recordHit();
|
||||
} else {
|
||||
stats.recordMiss();
|
||||
|
||||
// 如果有缓存加载器,尝试加载
|
||||
if (cacheLoader != null) {
|
||||
final long startTime = System.nanoTime();
|
||||
try {
|
||||
value = cacheLoader.apply(key);
|
||||
if (value != null) {
|
||||
delegate.put(key, value);
|
||||
stats.recordLoadSuccess(System.nanoTime() - startTime);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
stats.recordLoadFailure();
|
||||
throw new HutoolException("Failed to load cache value for key: " + key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key) {
|
||||
return get(key, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int prune() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
final int pruned = delegate.prune();
|
||||
if (enableStats && pruned > 0) {
|
||||
for (int i = 0; i < pruned; i++) {
|
||||
stats.recordEviction();
|
||||
}
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
return pruned;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.isFull();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final K key) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
delegate.remove(key);
|
||||
if (enableStats) {
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
delegate.clear();
|
||||
if (enableStats) {
|
||||
stats.setCacheSize(0);
|
||||
}
|
||||
pendingRefreshes.clear();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.capacity();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long timeout() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.timeout();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.isEmpty();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.size();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(final K key) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.containsKey(key);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 实现SmartCache接口方法 ==========
|
||||
|
||||
@Override
|
||||
public Map<K, V> getAll(final Collection<K> keys) {
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
final Map<K, V> result = new HashMap<>(keys.size());
|
||||
|
||||
for (final K key : keys) {
|
||||
final V value = get(key);
|
||||
if (value != null) {
|
||||
result.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(final Map<? extends K, ? extends V> map) {
|
||||
if (MapUtil.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
|
||||
delegate.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (enableStats) {
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<V> refreshAsync(final K key) {
|
||||
if (!enableAsyncRefresh) {
|
||||
throw new UnsupportedOperationException("Async refresh is not enabled");
|
||||
}
|
||||
|
||||
if (cacheLoader == null) {
|
||||
throw new IllegalStateException("Cache loader is required for async refresh");
|
||||
}
|
||||
|
||||
// 检查是否已经有正在进行的刷新
|
||||
final CompletableFuture<V> pending = pendingRefreshes.get(key);
|
||||
if (pending != null) {
|
||||
return pending;
|
||||
}
|
||||
|
||||
CompletableFuture<V> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
final long startTime = System.nanoTime();
|
||||
final V newValue = cacheLoader.apply(key);
|
||||
|
||||
if (newValue != null) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
delegate.put(key, newValue);
|
||||
if (enableStats) {
|
||||
stats.recordLoadSuccess(System.nanoTime() - startTime);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
return newValue;
|
||||
} catch (final Exception e) {
|
||||
if (enableStats) {
|
||||
stats.recordLoadFailure();
|
||||
}
|
||||
throw new CompletionException(e);
|
||||
} finally {
|
||||
pendingRefreshes.remove(key);
|
||||
}
|
||||
}, refreshExecutor);
|
||||
|
||||
// 设置超时
|
||||
future = future.orTimeout(refreshTimeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||
.exceptionally(ex -> {
|
||||
pendingRefreshes.remove(key);
|
||||
return null;
|
||||
});
|
||||
|
||||
pendingRefreshes.put(key, future);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int warmUp(final Collection<K> keys) {
|
||||
if (cacheLoader == null || CollUtil.isEmpty(keys)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int warmedUp = 0;
|
||||
final Collection<List<K>> batches = new Partition<>(new ArrayList<>(keys), warmUpBatchSize);
|
||||
|
||||
for (final List<K> batch : batches) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
for (final K key : batch) {
|
||||
if (!delegate.containsKey(key)) {
|
||||
try {
|
||||
final V value = cacheLoader.apply(key);
|
||||
if (value != null) {
|
||||
delegate.put(key, value);
|
||||
warmedUp++;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// 忽略单个键的加载失败,继续处理其他键
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (enableStats) {
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
|
||||
return warmedUp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfAbsent(final K key, final Function<K, V> mappingFunction) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
V value = delegate.get(key);
|
||||
if (value == null && mappingFunction != null) {
|
||||
final long startTime = System.nanoTime();
|
||||
try {
|
||||
value = mappingFunction.apply(key);
|
||||
if (value != null) {
|
||||
delegate.put(key, value);
|
||||
|
||||
if (enableStats) {
|
||||
stats.recordLoadSuccess(System.nanoTime() - startTime);
|
||||
stats.setCacheSize(delegate.size());
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
if (enableStats) {
|
||||
stats.recordLoadFailure();
|
||||
}
|
||||
throw new HutoolException("Failed to compute value for key: " + key, e);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfPresent(final K key, final Function<K, V> remappingFunction) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
if (delegate.containsKey(key) && remappingFunction != null) {
|
||||
final long startTime = System.nanoTime();
|
||||
try {
|
||||
final V newValue = remappingFunction.apply(key);
|
||||
if (newValue != null) {
|
||||
delegate.put(key, newValue);
|
||||
|
||||
if (enableStats) {
|
||||
stats.recordLoadSuccess(System.nanoTime() - startTime);
|
||||
}
|
||||
}
|
||||
return newValue;
|
||||
} catch (final Exception e) {
|
||||
if (enableStats) {
|
||||
stats.recordLoadFailure();
|
||||
}
|
||||
throw new HutoolException("Failed to compute value for key: " + key, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheStats getStats() {
|
||||
if (!enableStats) {
|
||||
throw new UnsupportedOperationException("Statistics are not enabled");
|
||||
}
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
stats.setCacheSize(delegate.size());
|
||||
return stats;
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearStats() {
|
||||
if (!enableStats) {
|
||||
throw new UnsupportedOperationException("Statistics are not enabled");
|
||||
}
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// 创建新的统计实例,保留缓存大小
|
||||
final long currentSize = stats.getCacheSize();
|
||||
stats.setCacheSize(currentSize);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(final String name) {
|
||||
this.name = StrUtil.defaultIfBlank(name, "SmartCache");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取底层缓存
|
||||
*
|
||||
* @return 底层缓存实例
|
||||
*/
|
||||
public Cache<K, V> getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier<V> valueFactory) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("Key must not be null");
|
||||
}
|
||||
|
||||
lock.readLock().lock();
|
||||
V value = null;
|
||||
try {
|
||||
// 1. 优先尝试从底层缓存获取
|
||||
value = delegate.get(key, isUpdateLastAccess);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
// 2. 如果缓存未命中,则使用工厂方法创建、缓存并返回新值
|
||||
if (value == null && valueFactory != null) {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// 双重检查锁定模式,防止在获取写锁期间,其他线程已经创建并插入了值
|
||||
value = delegate.get(key, isUpdateLastAccess);
|
||||
if (value == null) {
|
||||
// 记录加载开始时间,用于统计
|
||||
final long loadStartTime = System.nanoTime();
|
||||
try {
|
||||
// 调用工厂方法创建新值
|
||||
value = valueFactory.get();
|
||||
// 如果工厂成功创建了值,则将其放入缓存
|
||||
if (value != null) {
|
||||
if (timeout > 0) {
|
||||
// 使用传入的自定义超时时间
|
||||
delegate.put(key, value, timeout);
|
||||
} else {
|
||||
// 使用缓存的默认超时策略
|
||||
delegate.put(key, value);
|
||||
}
|
||||
|
||||
// 记录加载成功(如果开启了统计)
|
||||
if (enableStats) {
|
||||
stats.recordLoadSuccess(System.nanoTime() - loadStartTime);
|
||||
}
|
||||
} else {
|
||||
// 工厂方法返回了null,记录加载失败(可选逻辑)
|
||||
if (enableStats) {
|
||||
stats.recordLoadFailure();
|
||||
}
|
||||
// 注意:此时并未将null值存入缓存,下次请求仍会触发加载
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
if (enableStats) {
|
||||
stats.recordLoadFailure();
|
||||
}
|
||||
// 可以根据需要决定是抛出异常,还是返回null。
|
||||
// 为了保持接口的健壮性,这里将异常包装后抛出。
|
||||
throw new HutoolException("Failed to load value for key: " + key, e);
|
||||
}
|
||||
}
|
||||
// 无论新值是否由当前线程创建,写锁块结束时,value变量中已经有了最终结果。
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
// 返回最终结果
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> copiedIterator;
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
copiedIterator = CopiedIter.copyOf(this.delegate.cacheObjIterator());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
return new CacheObjIterator<>(copiedIterator);
|
||||
}
|
||||
}
|
||||
@@ -701,17 +701,43 @@ public class NumberUtil extends NumberValidator {
|
||||
|
||||
/**
|
||||
* 获得数字对应的二进制字符串
|
||||
* <ul>
|
||||
* <li>Integer/Long:直接使用 JDK 内置方法转换</li>
|
||||
* <li>Byte/Short:转换为无符号整数后补充前导零至对应位数(Byte=8位,Short=16位)</li>
|
||||
* <li>Float/Double:使用 IEEE 754 标准格式转换,Float=32位,Double=64位</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param number 数字
|
||||
* @param number 待转换的Number对象(支持Integer、Long、Byte、Short、Float、Double)
|
||||
* @return 二进制字符串
|
||||
*/
|
||||
public static String getBinaryStr(final Number number) {
|
||||
if (number instanceof Long) {
|
||||
return Long.toBinaryString((Long) number);
|
||||
} else if (number instanceof Integer) {
|
||||
return Integer.toBinaryString((Integer) number);
|
||||
} else {
|
||||
Assert.notNull(number, "Number must be not null!");
|
||||
|
||||
// 根据Number的实际类型处理
|
||||
if (number instanceof Integer) {
|
||||
return Integer.toBinaryString(number.intValue());
|
||||
} else if (number instanceof Long) {
|
||||
return Long.toBinaryString(number.longValue());
|
||||
} else if (number instanceof Byte) {
|
||||
// Byte是8位,补前导0至8位
|
||||
return String.format("%8s", Integer.toBinaryString(number.byteValue() & 0xFF)).replace(' ', '0');
|
||||
} else if (number instanceof Short) {
|
||||
// Short是16位,补前导0至16位
|
||||
return String.format("%16s", Integer.toBinaryString(number.shortValue() & 0xFFFF)).replace(' ', '0');
|
||||
} else if (number instanceof Float) {
|
||||
// Float转换为IEEE 754 32位二进制
|
||||
final int floatBits = Float.floatToIntBits(number.floatValue());
|
||||
return String.format("%32s", Integer.toBinaryString(floatBits)).replace(' ', '0');
|
||||
} else if (number instanceof Double) {
|
||||
// Double转换为IEEE 754 64位二进制
|
||||
final long doubleBits = Double.doubleToLongBits(number.doubleValue());
|
||||
return String.format("%64s", Long.toBinaryString(doubleBits)).replace(' ', '0');
|
||||
} else if (number instanceof BigInteger) {
|
||||
// 大数整数类型
|
||||
return ((BigInteger) number).toString(2);
|
||||
} else {
|
||||
// 不支持的类型(如BigInteger、BigDecimal需额外处理)
|
||||
throw new IllegalArgumentException("Number not support:" + number.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
package cn.hutool.v7.core.cache;
|
||||
|
||||
import cn.hutool.v7.core.cache.smart.SmartCache;
|
||||
import cn.hutool.v7.core.cache.smart.SmartCacheBuilder;
|
||||
import cn.hutool.v7.core.cache.smart.SmartCacheUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 智能缓存基础功能测试
|
||||
*/
|
||||
@DisplayName("智能缓存基础功能测试")
|
||||
public class SmartCacheBasicTest {
|
||||
|
||||
private SmartCache<Object, Object> cache;
|
||||
private AtomicInteger loadCounter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
loadCounter = new AtomicInteger(0);
|
||||
|
||||
cache = SmartCacheBuilder.of(CacheUtil.newLRUCache(10))
|
||||
.name("TestCache")
|
||||
.enableStats(true)
|
||||
.cacheLoader(key -> {
|
||||
loadCounter.incrementAndGet();
|
||||
// 模拟加载耗时
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return "value_" + key;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试基本put和get操作")
|
||||
void testBasicPutAndGet() {
|
||||
cache.put("key1", "value1");
|
||||
|
||||
assertEquals("value1", cache.get("key1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试缓存加载器")
|
||||
void testCacheLoader() {
|
||||
// 第一次获取,应该触发加载
|
||||
assertEquals("value_key2", cache.get("key2"));
|
||||
assertEquals(1, loadCounter.get());
|
||||
|
||||
// 第二次获取,应该使用缓存,不会触发加载
|
||||
assertEquals("value_key2", cache.get("key2"));
|
||||
assertEquals(1, loadCounter.get()); // 计数器不变
|
||||
|
||||
// 获取另一个键,应该再次触发加载
|
||||
assertEquals("value_key3", cache.get("key3"));
|
||||
assertEquals(2, loadCounter.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试批量操作")
|
||||
void testBatchOperations() {
|
||||
// 批量放入
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("batch1", "value1");
|
||||
data.put("batch2", "value2");
|
||||
data.put("batch3", "value3");
|
||||
|
||||
cache.putAll(data);
|
||||
|
||||
// 批量获取
|
||||
Map<Object, Object> result = cache.getAll(Arrays.asList("batch1", "batch2", "batch3", "non_existent"));
|
||||
|
||||
assertEquals(4, result.size());
|
||||
assertEquals("value1", result.get("batch1"));
|
||||
assertEquals("value2", result.get("batch2"));
|
||||
assertEquals("value3", result.get("batch3"));
|
||||
assertTrue(result.containsKey("non_existent"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试computeIfAbsent")
|
||||
void testComputeIfAbsent() {
|
||||
AtomicInteger computeCounter = new AtomicInteger(0);
|
||||
|
||||
// 第一次计算
|
||||
String result1 = (String) cache.computeIfAbsent("compute1", key -> {
|
||||
computeCounter.incrementAndGet();
|
||||
return "computed_" + key;
|
||||
});
|
||||
|
||||
assertEquals("computed_compute1", result1);
|
||||
assertEquals(1, computeCounter.get());
|
||||
|
||||
// 第二次获取,应该使用缓存
|
||||
String result2 = (String) cache.computeIfAbsent("compute1", key -> {
|
||||
computeCounter.incrementAndGet();
|
||||
return "should_not_be_called";
|
||||
});
|
||||
|
||||
assertEquals("computed_compute1", result2);
|
||||
assertEquals(1, computeCounter.get()); // 计数器不变
|
||||
|
||||
// 测试不存在的键
|
||||
assertNull(cache.computeIfAbsent("nullKey", key -> null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试缓存预热")
|
||||
void testWarmUp() {
|
||||
// 清除初始状态
|
||||
cache.clear();
|
||||
|
||||
// 预热
|
||||
int warmed = cache.warmUp(Arrays.asList("warm1", "warm2", "warm3"));
|
||||
|
||||
assertEquals(3, warmed);
|
||||
assertEquals(3, cache.size());
|
||||
|
||||
// 验证预热的内容
|
||||
assertEquals("value_warm1", cache.get("warm1"));
|
||||
assertEquals("value_warm2", cache.get("warm2"));
|
||||
assertEquals("value_warm3", cache.get("warm3"));
|
||||
|
||||
// 预热已存在的键,应该不会重复加载
|
||||
int alreadyWarmed = cache.warmUp(Arrays.asList("warm1", "warm4"));
|
||||
assertEquals(1, alreadyWarmed); // 只有warm4是新加载的
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试缓存容量和大小")
|
||||
void testCapacityAndSize() {
|
||||
SmartCache<String, String> smallCache = SmartCacheUtil.newLRUSmartCache(3);
|
||||
|
||||
smallCache.put("1", "a");
|
||||
smallCache.put("2", "b");
|
||||
smallCache.put("3", "c");
|
||||
|
||||
assertEquals(3, smallCache.size());
|
||||
assertEquals(3, smallCache.capacity());
|
||||
|
||||
// 超过容量,应该触发淘汰
|
||||
smallCache.put("4", "d");
|
||||
|
||||
// 由于是LRU,第一个元素可能被淘汰
|
||||
assertTrue(smallCache.size() <= 3);
|
||||
}
|
||||
}
|
||||
@@ -834,4 +834,11 @@ public class NumberUtilTest {
|
||||
final Number number = NumberUtil.parseNumber("12,234,456");
|
||||
assertEquals(new BigDecimal(12234456), number);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFloatBinaryStr() {
|
||||
// 获取浮点数的 IEEE 754 原始比特位字符串
|
||||
final String result = NumberUtil.getBinaryStr(3.5);
|
||||
assertEquals("0100000000001100000000000000000000000000000000000000000000000000", result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user