新增UUID v7生成器

编写对应的单元测试
This commit is contained in:
Cason 2024-08-30 22:50:39 +08:00
parent 21858539e5
commit e90986ff19
3 changed files with 147 additions and 10 deletions

View File

@ -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) {

View File

@ -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 identifierUUID实现UUID表示一个128位的值<br>
@ -82,6 +83,9 @@ public class UUID implements java.io.Serializable, Comparable<UUID> {
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<UUID> {
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<UUID> {
// 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;

View File

@ -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<org.dromara.hutool.core.data.id.UUID> 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());
}
}