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());
+ }
}