From fd7ebc3eb983434786f83e52a2b537ab43816957 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 9 Dec 2025 15:39:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D`NumberUtil.getBinaryStr`?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E8=AE=A1=E7=AE=97Double=E7=AD=89=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1=E5=B0=8F=E6=95=B0=E9=97=AE=E9=A2=98=EF=BC=88pr#1411@G?= =?UTF-8?q?itee=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7/core/cache/impl/SmartCacheImpl.java | 576 ------------------ .../cn/hutool/v7/core/math/NumberUtil.java | 38 +- .../v7/core/cache/SmartCacheBasicTest.java | 156 ----- .../hutool/v7/core/math/NumberUtilTest.java | 7 + 4 files changed, 39 insertions(+), 738 deletions(-) delete mode 100644 hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java delete mode 100644 hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java deleted file mode 100644 index daa89c241e..0000000000 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/SmartCacheImpl.java +++ /dev/null @@ -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 缓存键类型 - * @param 缓存值类型 - * @author Nic - */ -public class SmartCacheImpl implements SmartCache { - - // 底层缓存 - private final Cache 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 cacheLoader; - - // 统计信息 - private final CacheStats stats; - - // 锁机制 - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final Map> pendingRefreshes = new ConcurrentHashMap<>(); - - /** - * 构造函数 - * - * @param delegate 底层缓存实现 - * @param name 缓存名称 - * @param enableStats 是否启用统计信息 - * @param enableAsyncRefresh 是否启用异步刷新 - * @param warmUpBatchSize warmUpBatchSize - * @param refreshTimeout 刷新超时时间 - * @param refreshExecutor 刷新执行器 - * @param cacheLoader 缓存加载器 - */ - public SmartCacheImpl( - final Cache delegate, - final String name, - final boolean enableStats, - final boolean enableAsyncRefresh, - final int warmUpBatchSize, - final Duration refreshTimeout, - final ExecutorService refreshExecutor, - final Function 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 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 getAll(final Collection keys) { - if (CollUtil.isEmpty(keys)) { - return Collections.emptyMap(); - } - - lock.readLock().lock(); - try { - final Map 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 map) { - if (MapUtil.isEmpty(map)) { - return; - } - - lock.writeLock().lock(); - try { - for (final Map.Entry entry : map.entrySet()) { - delegate.put(entry.getKey(), entry.getValue()); - } - - if (enableStats) { - stats.setCacheSize(delegate.size()); - } - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public CompletableFuture 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 pending = pendingRefreshes.get(key); - if (pending != null) { - return pending; - } - - CompletableFuture 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 keys) { - if (cacheLoader == null || CollUtil.isEmpty(keys)) { - return 0; - } - - int warmedUp = 0; - final Collection> batches = new Partition<>(new ArrayList<>(keys), warmUpBatchSize); - - for (final List 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 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 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 getDelegate() { - return delegate; - } - - @Override - public V get(final K key, final boolean isUpdateLastAccess, final long timeout, final SerSupplier 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> cacheObjIterator() { - CopiedIter> copiedIterator; - lock.readLock().lock(); - try { - copiedIterator = CopiedIter.copyOf(this.delegate.cacheObjIterator()); - } finally { - lock.readLock().unlock(); - } - return new CacheObjIterator<>(copiedIterator); - } -} diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/math/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/v7/core/math/NumberUtil.java index 667956222c..5aa546012b 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/math/NumberUtil.java @@ -701,17 +701,43 @@ public class NumberUtil extends NumberValidator { /** * 获得数字对应的二进制字符串 + *
    + *
  • Integer/Long:直接使用 JDK 内置方法转换
  • + *
  • Byte/Short:转换为无符号整数后补充前导零至对应位数(Byte=8位,Short=16位)
  • + *
  • Float/Double:使用 IEEE 754 标准格式转换,Float=32位,Double=64位
  • + *
* - * @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()); } } diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java deleted file mode 100644 index 9ad9920542..0000000000 --- a/hutool-core/src/test/java/cn/hutool/v7/core/cache/SmartCacheBasicTest.java +++ /dev/null @@ -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 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 data = new HashMap<>(); - data.put("batch1", "value1"); - data.put("batch2", "value2"); - data.put("batch3", "value3"); - - cache.putAll(data); - - // 批量获取 - Map 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 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); - } -} diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberUtilTest.java index f7c27f11cd..8fdef3bc99 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberUtilTest.java @@ -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); + } }