一个唯一的ID可以使用UUID,但不是顺序的。
一个自增的ID可以使用数据库序列、自增主键、雪花算法等等。
本文分享一个简单实用的一个ID生成代码,支持生成顺序自增且唯一的ID,一个工具类可以直接拷贝使用,简单、轻量。
原理:
ID组成规则: yyyyMMddHHmmssSSS + 自定义的位数(每秒并发数量)
生成逻辑:每毫秒时间戳开始加自定义的位数如4位 20220526111200000+0000,如果该时间内已经生成过该ID,那么自定义的位数自增1(20220526111200000+0001),即可认为每毫秒支持生成自定义位数的唯一ID(每毫秒多少并发)
代码如下:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UniqueSeqUtil {
private static volatile int seq = 0;
private static volatile long currentTimeMillis;
public static class UniqueId {
private final long currentTimeMillis;
private final String id;
private final String compensate;
public UniqueId(long currentTimeMillis, String id, String compensate) {
this.currentTimeMillis = currentTimeMillis;
this.id = id;
this.compensate = compensate;
}
/**
* 生成该id时候的时间戳
*/
public long getCurrentTimeMillis() {
return currentTimeMillis;
}
/**
* 唯一ID
*/
public String getId() {
return id;
}
/**
* 补全的位,该时间戳的第几个
*/
public String getCompensate() {
return compensate;
}
}
/**
* 生成一个唯一的订单序列,长度为 yyyyMMddHHmmssSSS + compensateI
*
* @param compensateI 4 代表长度加4位 ,支持每毫秒4位的并发
* @return
*/
public final static synchronized UniqueId uniqueId(int compensateI) {
final long ctm = System.currentTimeMillis();
if (currentTimeMillis != ctm) {
seq = 0;
currentTimeMillis = ctm;
}
String compensate = "";// 补零
String sqlStr = String.valueOf(++seq);
int length = sqlStr.length();
for (int i = 0; i < compensateI - length; i++) {
compensate += "0";
}
String currentTimeStr = (new SimpleDateFormat("yyyyMMddHHmmssSSS")).format(new Date(ctm));
String id = currentTimeStr + compensate + sqlStr;
return new UniqueId(ctm, id, compensate);
}
private static volatile long currentTimeMillis1;
/**
* 生成一个唯一序列yyyyMMddHHmmssSSS,延迟一毫秒生成,多线程调用那么串行
*/
public static String yyyyMMddHHmmssSSS() {
long ctm;
synchronized (UniqueSeqUtil.class) {
while (true) {
ctm = System.currentTimeMillis();
if (currentTimeMillis1 != ctm) {
currentTimeMillis1 = ctm;
break;
}
}
}
String currentTimeStr = (new SimpleDateFormat("yyyyMMddHHmmssSSS")).format(new Date(ctm));
return currentTimeStr;
}
}
测试该工具类生成ID是否安全,使用一个线程安全的Map去记录生成的ID,如果发现重复的ID那么就抛出异常
代码如下 :
public static void main(String[] args) {
final Map<String,Integer> aa=new ConcurrentHashMap<String, Integer>();
for (int i = 0; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
String key = uniqueId(4).getId();
if(aa.containsKey(key)) {
throw new RuntimeException("重复");
}else {
aa.put(key, 1);
System.out.println(key+"\t"+aa.size());
}
}
}
}).start();
}
}
执行结果:
测试发现生成速度还是很快的(具体读者可以自己测试,感觉一秒可以生成上万个ID),下图可以验证说明程序生成的ID是不会重复的(程序可靠)
总结:当然以上的程序性能并非是最优的,读者可以优化,若加上服务器ID号,就可以用来做分布式ID生成器,
如:ID规则 = 服务器ID + yyyyMMddHHmmssSSS + 自定义的位数(每秒并发数量)