From d89571f82d1f06db3e772453161daaa5499c335a Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 Aug 2025 17:32:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D`NumberWordFormatter`formatSi?= =?UTF-8?q?mple=E8=BE=93=E5=87=BA=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=88pr#4034@Github=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v7/core/cache/impl/AbstractCache.java | 4 - .../v7/core/cache/impl/LockedCache.java | 2 +- .../hutool/v7/core/cache/impl/TimedCache.java | 2 + .../hutool/v7/core/cache/impl/WeakCache.java | 3 + .../v7/core/math/EnglishNumberFormatter.java | 76 +++++++++++++------ .../cn/hutool/v7/core/math/NumberUtil.java | 1 + .../cn/hutool/v7/core/cache/CacheTest.java | 8 +- .../v7/core/math/NumberWordFormatTest.java | 48 ++++++++++++ .../v7/swing/captcha/AbstractCaptcha.java | 6 +- 9 files changed, 112 insertions(+), 38 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java index 9389c6bbfc..b7cca6ff64 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/AbstractCache.java @@ -18,7 +18,6 @@ package cn.hutool.v7.core.cache.impl; import cn.hutool.v7.core.cache.Cache; import cn.hutool.v7.core.cache.CacheListener; -import cn.hutool.v7.core.func.SerSupplier; import cn.hutool.v7.core.lang.mutable.Mutable; import cn.hutool.v7.core.lang.mutable.MutableObj; @@ -26,10 +25,7 @@ import java.io.Serial; import java.util.Iterator; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java index 2a7254a303..8e7b7e0dfb 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/LockedCache.java @@ -80,7 +80,7 @@ public abstract class LockedCache extends AbstractCache { if (null == co) { // supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥 v = valueFactory.get(); - put(key, v, timeout); + putWithoutLock(key, v, timeout); } } finally { lock.unlock(); diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedCache.java index 5e69d7190e..03e3187011 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/TimedCache.java @@ -21,6 +21,7 @@ import cn.hutool.v7.core.lang.Assert; import cn.hutool.v7.core.lang.mutable.Mutable; import cn.hutool.v7.core.thread.lock.NoLock; +import java.io.Serial; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -40,6 +41,7 @@ import java.util.concurrent.locks.ReentrantLock; * @param 值类型 */ public class TimedCache extends LockedCache { + @Serial private static final long serialVersionUID = 1L; /** 正在执行的定时任务 */ diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/WeakCache.java b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/WeakCache.java index 6648ab3ad6..2923c5ab4b 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/WeakCache.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/cache/impl/WeakCache.java @@ -22,6 +22,8 @@ import cn.hutool.v7.core.lang.mutable.Mutable; import cn.hutool.v7.core.lang.ref.Ref; import cn.hutool.v7.core.map.reference.WeakConcurrentMap; +import java.io.Serial; + /** * 弱引用缓存
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。
@@ -35,6 +37,7 @@ import cn.hutool.v7.core.map.reference.WeakConcurrentMap; * @since 3.0.7 */ public class WeakCache extends TimedCache{ + @Serial private static final long serialVersionUID = 1L; /** diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/math/EnglishNumberFormatter.java b/hutool-core/src/main/java/cn/hutool/v7/core/math/EnglishNumberFormatter.java index 6955294783..28d4cb081c 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/math/EnglishNumberFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/math/EnglishNumberFormatter.java @@ -35,7 +35,10 @@ public class EnglishNumberFormatter { "SEVENTY", "EIGHTY", "NINETY"}; private static final String[] NUMBER_MORE = new String[]{"", "THOUSAND", "MILLION", "BILLION", "TRILLION"}; - private static final String[] NUMBER_SUFFIX = new String[]{"k", "w", "", "m", "", "", "b", "", "", "t", "", "", "p", "", "", "e"}; +// private static final String[] NUMBER_SUFFIX = new String[]{"k", "w", "", "m", "", "", "b", "", "", "t", "", "", "p", "", "", "e"}; + private static final String[] NUMBER_SUFFIX = new String[] { "k", "w", "m", "b", "t", "p", "e" }; + // 标准单位序列(k, m, b, t, p, e)在NUMBER_SUFFIX中的索引 + private static final int[] STANDARD_UNIT_INDICES = { 0, 2, 3, 4, 5, 6 }; /** * 将阿拉伯数字转为英文表达式 @@ -64,30 +67,53 @@ public class EnglishNumberFormatter { } /** - * 将阿拉伯数字转化为简介计数单位,例如 2100 =》 2.1k + * 将数字转换为简写形式,格式单位简写为:k(千), m(百万), b(十亿), t(万亿),如果中文单位,为w(万) + *
    + *
  • 1000 =》 1k
  • + *
  • 10000 =》 10k,如果是中文单位,则为1w
  • + *
  • 100000 =》 100k,如果是中文单位,则为10w
  • + *
* - * @param value 对应数字的值 - * @param isTwo 控制是否为只为k、w,例如当为{@code false}时返回4.38m,{@code true}返回438.43w - * @return 格式化后的数字 - * @since 5.5.9 + * @param number 要转换的数字 + * @param useChineseUnit 是否使用中文单位(w等) + * @return 简写形式的字符串 */ - public static String formatSimple(final long value, final boolean isTwo) { - if (value < 1000) { - return String.valueOf(value); + public static String formatSimple(final long number, final boolean useChineseUnit) { + if (number < 1_000) { + return String.valueOf(number); } - int index = -1; - double res = value; - while (res > 10 && (!isTwo || index < 1)) { - if (res >= 1000) { - res = res / 1000; - index++; - } - if (res > 10) { - res = res / 10; - index++; + + double value; + String suffix; + + // 使用国际单位系统:k(千), m(百万), b(十亿), t(万亿) + if (number < 1_000_000) { + value = number / 1_000.0; + suffix = "k"; + } else if (number < 1_000_000_000) { + value = number / 1_000_000.0; + suffix = "m"; + } else if (number < 1_000_000_000_000L) { + value = number / 1_000_000_000.0; + suffix = "b"; + } else { + value = number / 1_000_000_000_000.0; + suffix = "t"; + } + + // 兼容中文简写形式,如10k->1w + if (useChineseUnit) { + if("m".equals(suffix)){ + suffix = "w"; + value *= 100; + } else if("k".equals(suffix) && value >= 10){ + suffix = "w"; + value /= 10; } } - return String.format("%s%s", NumberUtil.format("#.##", res), NUMBER_SUFFIX[index]); + + // 格式化数字,最多保留2位小数,去除尾部的0 + return NumberUtil.format("#.##", value) + suffix; } /** @@ -97,9 +123,9 @@ public class EnglishNumberFormatter { * @return 英文表达式 */ private static String format(final String x) { - final int z = x.indexOf("."); // 取小数点位置 + final int z = x.indexOf(StrUtil.DOT); // 取小数点位置 final String lstr; - String rstr = ""; + String rstr = StrUtil.EMPTY; if (z > -1) { // 看是否有小数,如果有,则分别取左边和右边 lstr = x.substring(0, z); rstr = x.substring(z + 1); @@ -125,7 +151,7 @@ public class EnglishNumberFormatter { if (!"000".equals(a[i])) { // 用来避免这种情况:1000000 = one million // thousand only if (i != 0) { - lm.insert(0, transThree(a[i]) + " " + parseMore(i) + " "); // 加: + lm.insert(0, transThree(a[i]) + StrUtil.SPACE + parseMore(i) + StrUtil.SPACE); // 加: // thousand、million、billion } else { // 防止i=0时, 在多加两个空格. @@ -136,9 +162,9 @@ public class EnglishNumberFormatter { } } - String xs = lm.length() == 0 ? "ZERO " : " "; // 用来存放转换后小数部分 + String xs = lm.isEmpty() ? "ZERO " : StrUtil.SPACE; // 用来存放转换后小数部分 if (z > -1) { - xs += "AND CENTS " + transTwo(rstr) + " "; // 小数部分存在时转换小数 + xs += "AND CENTS " + transTwo(rstr) + StrUtil.SPACE; // 小数部分存在时转换小数 } return lm.toString().trim() + xs + "ONLY"; 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 eed3c91d2d..667956222c 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 @@ -472,6 +472,7 @@ public class NumberUtil extends NumberValidator { *
  • 00.000 =》 取两位整数和三位小数
  • *
  • # =》 取所有整数部分
  • *
  • #.##% =》 以百分比方式计数,并取两位小数
  • + *
  • #.## =》 取两位小数,小数部分为0时忽略
  • *
  • #.#####E0 =》 显示为科学计数法,并取五位小数
  • *
  • ,### =》 每三位以逗号进行分隔,例如:299,792,458
  • *
  • 光速大小为每秒,###米 =》 将格式嵌入文本
  • diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java index 60955b48e5..e33cfab24d 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/cache/CacheTest.java @@ -148,7 +148,7 @@ public class CacheTest { * https://gitee.com/chinabugotech/hutool/issues/IBP752 */ @Test - public void whenContainsKeyTimeout_shouldCallOnRemove() { + public void whenContainsKeyTimeoutShouldCallOnRemove() { final int timeout = 50; final TimedCache ALARM_CACHE = new TimedCache<>(timeout); @@ -170,9 +170,9 @@ public class CacheTest { * https://github.com/chinabugotech/hutool/issues/3957 */ @Test - public void reentrantCache_clear_Method_Test() { - AtomicInteger removeCount = new AtomicInteger(); - Cache lruCache = CacheUtil.newLRUCache(4); + public void reentrantCacheClearMethodTest() { + final AtomicInteger removeCount = new AtomicInteger(); + final Cache lruCache = CacheUtil.newLRUCache(4); lruCache.setListener((key, cachedObject) -> removeCount.getAndIncrement()); lruCache.put("key1","String1"); lruCache.put("key2","String2"); diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberWordFormatTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberWordFormatTest.java index c27574c044..80120144b9 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberWordFormatTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/math/NumberWordFormatTest.java @@ -56,4 +56,52 @@ public class NumberWordFormatTest { final String s = EnglishNumberFormatter.formatSimple(1000); Assertions.assertEquals("1k", s); } + + @Test + public void issue4033Test(){ + String s = EnglishNumberFormatter.formatSimple(1_000, false); + Assertions.assertEquals("1k", s); + + s = EnglishNumberFormatter.formatSimple(10_000, false); + Assertions.assertEquals("10k", s); + + s = EnglishNumberFormatter.formatSimple(100_000, false); + Assertions.assertEquals("100k", s); + + s = EnglishNumberFormatter.formatSimple(1_000_000, false); + Assertions.assertEquals("1m", s); + + s = EnglishNumberFormatter.formatSimple(10_000_000, false); + Assertions.assertEquals("10m", s); + + s = EnglishNumberFormatter.formatSimple(100_000_000, false); + Assertions.assertEquals("100m", s); + + s = EnglishNumberFormatter.formatSimple(1_000_000_000, false); + Assertions.assertEquals("1b", s); + } + + @Test + public void issue4033Test2(){ + String s = EnglishNumberFormatter.formatSimple(1_000, true); + Assertions.assertEquals("1k", s); + + s = EnglishNumberFormatter.formatSimple(10_000, true); + Assertions.assertEquals("1w", s); + + s = EnglishNumberFormatter.formatSimple(100_000, true); + Assertions.assertEquals("10w", s); + + s = EnglishNumberFormatter.formatSimple(1_000_000, true); + Assertions.assertEquals("100w", s); + + s = EnglishNumberFormatter.formatSimple(10_000_000, true); + Assertions.assertEquals("1000w", s); + + s = EnglishNumberFormatter.formatSimple(100_000_000, true); + Assertions.assertEquals("10000w", s); + + s = EnglishNumberFormatter.formatSimple(1_000_000_000, true); + Assertions.assertEquals("1b", s); + } } diff --git a/hutool-swing/src/main/java/cn/hutool/v7/swing/captcha/AbstractCaptcha.java b/hutool-swing/src/main/java/cn/hutool/v7/swing/captcha/AbstractCaptcha.java index a4d544e333..71f780d657 100644 --- a/hutool-swing/src/main/java/cn/hutool/v7/swing/captcha/AbstractCaptcha.java +++ b/hutool-swing/src/main/java/cn/hutool/v7/swing/captcha/AbstractCaptcha.java @@ -30,10 +30,7 @@ import java.awt.Color; import java.awt.Font; import java.awt.Image; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; /** * 抽象验证码
    @@ -43,6 +40,7 @@ import java.io.OutputStream; * @author Looly */ public abstract class AbstractCaptcha implements ICaptcha { + @Serial private static final long serialVersionUID = 3180820918087507254L; /**