diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java index b80037fa4..42cb3017b 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/IdUtil.java @@ -77,6 +77,10 @@ public class IdUtil { return UUID.fastUUID().toString(true); } + public static String randomUUID7() { + return UUID.randomUUID7().toString(); + } + // endregion // region ----- ObjectId @@ -259,7 +263,7 @@ public class IdUtil { } if (null != mac) { id = ((0x000000FF & (long) mac[mac.length - 2]) - | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; id = id % (maxDatacenterId + 1); } @@ -305,7 +309,6 @@ public class IdUtil { * 则会使用此默认实例。 * * @return SeataSnowflake 返回一个默认配置的SeataSnowflake实例。 - * * @see IdConstants#DEFAULT_SNOWFLAKE */ public static SeataSnowflake getSeataSnowflake() { @@ -320,7 +323,6 @@ public class IdUtil { * * @param nodeId 节点ID * @return SeataSnowflake 返回一个自定义配置的SeataSnowflake实例。 - * * @see IdConstants#DEFAULT_SEATA_SNOWFLAKE */ public static SeataSnowflake getSeataSnowflake(final long nodeId) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/UUID.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/UUID.java index 7dc6f6470..7aa8aa54a 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/UUID.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/UUID.java @@ -24,6 +24,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; /** * 提供通用唯一识别码(universally unique identifier)(UUID)实现,UUID表示一个128位的值。
@@ -82,6 +83,9 @@ public class UUID implements java.io.Serializable, Comparable { static final SecureRandom NUMBER_GENERATOR = RandomUtil.getSecureRandom(); } + private static final AtomicLong lastV7time = new AtomicLong(0); + private static final long NANOS_PER_MILLI = 1_000_000; + private final Number128 idValue; /** @@ -178,6 +182,62 @@ public class UUID implements java.io.Serializable, Comparable { return new UUID(md5Bytes); } + /** + * 获取随机生成的UUIDv7 + * + * @return UUIDv7 + */ + public static UUID randomUUID7() { + byte[] randomBytes = new byte[16]; + final Random ng = Holder.NUMBER_GENERATOR; + ng.nextBytes(randomBytes); + + long[] v7Time = getV7Time(); + long milli = v7Time[0]; + long seq = v7Time[1]; + + randomBytes[0] = (byte) (milli >> 40); + randomBytes[1] = (byte) (milli >> 32); + randomBytes[2] = (byte) (milli >> 24); + randomBytes[3] = (byte) (milli >> 16); + randomBytes[4] = (byte) (milli >> 8); + randomBytes[5] = (byte) milli; + + randomBytes[6] = (byte) ((0x70) | (0x0F & (seq >> 8))); + randomBytes[7] = (byte) seq; + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + + return new UUID(convertToLong(randomBytes, 0), convertToLong(randomBytes, 8)); + } + + private static long[] getV7Time() { + long nano = System.nanoTime(); + long milli = nano / NANOS_PER_MILLI; + long seq = (nano - milli * NANOS_PER_MILLI) >> 8; + long now = (milli << 12) + seq; + + while (true) { + long last = lastV7time.get(); + if (now <= last) { + now = last + 1; + milli = now >> 12; + seq = now & 0xfff; + } + if (lastV7time.compareAndSet(last, now)) { + return new long[]{milli, seq}; + } + } + } + + private static long convertToLong(byte[] bytes, int start) { + long result = 0; + for (int i = start; i < start + 8; i++) { + result = (result << 8) | (bytes[i] & 0xFF); + } + return result; + } + /** * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 * @@ -453,7 +513,7 @@ public class UUID implements java.io.Serializable, Comparable { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers int compare = Long.compare(this.getMostSignificantBits(), val.getMostSignificantBits()); - if(0 == compare){ + if (0 == compare) { compare = Long.compare(this.getLeastSignificantBits(), val.getLeastSignificantBits()); } return compare; diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/util/IdUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/util/IdUtilTest.java index 9bec5c54c..b7610f485 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/util/IdUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/util/IdUtilTest.java @@ -22,17 +22,21 @@ import org.dromara.hutool.core.data.id.Snowflake; import org.dromara.hutool.core.thread.ThreadUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * {@link IdUtil} 单元测试 * * @author looly - * */ public class IdUtilTest { @@ -101,9 +105,9 @@ public class IdUtilTest { //每个线程生成的ID数 final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); - for(int i =0; i < threadCount; i++) { + for (int i = 0; i < threadCount; i++) { ThreadUtil.execute(() -> { - for(int i1 = 0; i1 < idCountPerThread; i1++) { + for (int i1 = 0; i1 < idCountPerThread; i1++) { final long id = snowflake.next(); set.add(id); // Console.log("Add new id: {}", id); @@ -131,9 +135,9 @@ public class IdUtilTest { //每个线程生成的ID数 final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); - for(int i =0; i < threadCount; i++) { + for (int i = 0; i < threadCount; i++) { ThreadUtil.execute(() -> { - for(int i1 = 0; i1 < idCountPerThread; i1++) { + for (int i1 = 0; i1 < idCountPerThread; i1++) { final long id = IdUtil.getSnowflake(1, 1).next(); set.add(id); // Console.log("Add new id: {}", id); @@ -152,9 +156,80 @@ public class IdUtilTest { } @Test - public void getDataCenterIdTest(){ + public void getDataCenterIdTest() { //按照mac地址算法拼接的算法,maxDatacenterId应该是0xffffffffL>>6-1此处暂时按照0x7fffffffffffffffL-1,防止最后取模溢出 final long dataCenterId = IdUtil.getDataCenterId(Long.MAX_VALUE); Assertions.assertTrue(dataCenterId >= 0); } + + + @Test + public void testUUIDv7Format() { + org.dromara.hutool.core.data.id.UUID uuid = org.dromara.hutool.core.data.id.UUID.randomUUID7(); + String uuidStr = uuid.toString(); + + // 验证UUID字符串格式是否符合标准 + assertTrue(uuidStr.matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")); + } + + @Test + public void testUUIDv7Properties() { + org.dromara.hutool.core.data.id.UUID uuid = org.dromara.hutool.core.data.id.UUID.randomUUID7(); + + // 验证版本号是否为7 + assertEquals(7, uuid.version()); + + // 验证变体是否为IETF variant + assertEquals(2, uuid.variant()); + + } + + @RepeatedTest(10) + public void testUUIDv7Uniqueness() { + Set uuids = new HashSet<>(); + + // 生成100万个UUIDv7,验证是否有重复 + for (int i = 0; i < 1000000; i++) { + org.dromara.hutool.core.data.id.UUID uuid = org.dromara.hutool.core.data.id.UUID.randomUUID7(); + assertFalse(uuids.contains(uuid)); + uuids.add(uuid); + } + } + + + @Test + public void testUUIDv7Monotonicity() { + org.dromara.hutool.core.data.id.UUID prev = org.dromara.hutool.core.data.id.UUID.randomUUID7(); + + // 验证连续生成的1000个UUIDv7是否呈单调递增趋势 + for (int i = 0; i < 1000; i++) { + org.dromara.hutool.core.data.id.UUID next = org.dromara.hutool.core.data.id.UUID.randomUUID7(); + assertTrue(next.compareTo(prev) > 0); + prev = next; + } + } + + /** + * UUIDv7的性能测试 + */ + @Test + public void uuidv7BenchTest() { + final StopWatch timer = DateUtil.createStopWatch(); + + // UUID v7 generation benchmark + timer.start("UUID v7 generation"); + for (int i = 0; i < 1000000; i++) { + IdUtil.randomUUID7(); + } + timer.stop(); + Console.log("UUIDv7 generation time: {} ms", timer.getLastTaskTimeMillis()); + + + timer.start("UUID v7 generation and formatting"); + for (int i = 0; i < 1000000; i++) { + IdUtil.randomUUID7().replace("-", ""); + } + timer.stop(); + Console.log("UUIDv7 generation and formatting time: {} ms", timer.getLastTaskTimeMillis()); + } }