简单生成器
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* @version 1.0.0
* @ClassName IdUtil.java
* @Description 随机数生成
*/
public final class IdUtil {
private static final String DATE_PATTERN = "yyyyMMdd";
private IdUtil() {
}
/**
* 生成18位数字
* 说明:循环10万左右会有重复ID
*
* @return
*/
public static Long generate18Number() {
//随机生成一位整数
int random = (int) (Math.random() * 9 + 1);
String prefix = String.valueOf(random);
//格式化日期,生成8位数字
String middle = LocalDate.now().format(DateTimeFormatter.ofPattern(DATE_PATTERN));
//生成uuid的hashCode值
int hashCode = UUID.randomUUID().toString().hashCode();
//可能为负数
if (hashCode < 0) {
hashCode = -hashCode;
}
String code = String.valueOf(hashCode);
if (code.length() > 9) {
hashCode = Integer.parseInt(code.substring(1));
}
String value = prefix + middle + String.format("%09d", hashCode);
return Long.valueOf(value);
}
}
基于雪花算法实现
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* @ClassName TidGenerator.java
* @Description 可以与Spring结合给该类添加@Component注解,确保只有一个实例
*/
public class TidGenerator {
/**
* 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
*/
private final static long twepoch = 1626923889730L;
/**
* 机器标识位数
*/
private final static long workerIdBits = 5L;
/**
* 数据中心标识位数
*/
private final static long dataCenterIdBits = 5L;
/**
* 机器ID最大值
*/
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 数据中心ID最大值
*/
private final static long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
/**
* 毫秒内自增位
*/
private final static long sequenceBits = 12L;
/**
* 机器ID偏左移12位
*/
private final static long workerIdShift = sequenceBits;
/**
* 数据中心ID左移17位
*/
private final static long dataCenterIdShift = sequenceBits + workerIdBits;
/**
* 时间毫秒左移22位
*/
private final static long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 上次生产id时间戳
*/
private static long lastTimestamp = -1L;
/**
* 0,并发控制
*/
private long sequence = 0L;
private final long workerId;
/**
* 数据标识id部分
*/
private final long dataCenterId;
/**
* 默认的TID长度
*/
private final static int defaultTidLength = 12;
private static final String SPECIAL_CHARS = "!@#$%^&*_=+-/";
public TidGenerator() {
this.dataCenterId = getDataCenterId(maxDataCenterId);
this.workerId = getMaxWorkerId(dataCenterId, maxWorkerId);
}
/**
* @param workerId 工作机器ID
* @param dataCenterId 序列号
*/
public TidGenerator(long workerId, long dataCenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
throw new IllegalArgumentException(String.format("data center Id can't be greater than %d or less than 0", maxDataCenterId));
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextSequenceId() {
long timestamp = timeGen();
//检查是否出现了「时钟回拨」问题
timestamp = checkClockMovedBackwards(timestamp);
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (dataCenterId << dataCenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
/**
* 产生下一个TID
*/
public synchronized String nextTid() {
//1. 生成一个有序序列
long sequence = nextSequenceId();
//2. 转化为62进制
String str = this.decimalToSixtyTwo(sequence);
//3. 补全12位并返回
return completeDigits(str, defaultTidLength);
}
/**
* 产生下一个指定长度的TID
*/
public synchronized String nextTid(int length) {
//1. 生成一个有序序列
long sequence = nextSequenceId();
//2. 转化为62进制
String str = this.decimalToSixtyTwo(sequence);
//3. 补全length位并返回
return completeDigits(str, length);
}
/**
* 获取至少18位的整数
*
* @return
*/
public synchronized long getAtLeast18BitSequenceId() {
long seqId = this.nextSequenceId();
String seq = String.valueOf(seqId);
if (seq.length() >= 18) {
return seqId;
}
if (seq.length() < 18) {
//补全位数
//随机生成一位整数
int random = (int) (Math.random() * 9 + 1);
String prefix = String.valueOf(random);
String value = prefix + String.format("%017d", seqId);
return Long.parseLong(value);
}
return seqId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* 获取 maxWorkerId
*/
protected static long getMaxWorkerId(long dataCenterId, long maxWorkerId) {
StringBuffer mpId = new StringBuffer();
mpId.append(dataCenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpId.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* 数据标识id部分
*/
protected static long getDataCenterId(long maxDataCenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDataCenterId + 1);
}
} catch (Exception e) {
e.printStackTrace();
}
return id;
}
/**
* 检查是否出现了「时钟回拨」问题
*/
private long checkClockMovedBackwards(long nowStamp) {
if (nowStamp >= lastTimestamp) {
return nowStamp;
}
//如果因为某些原因出现了「时钟回拨」问题
long diff = lastTimestamp - nowStamp;
//如果时间回拨超过1秒钟,则对外抛出异常,否则暂停1秒钟后再次尝试获取
if (diff >= TimeUnit.SECONDS.toMillis(1)) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", diff));
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException("An error occurred here", e);
}
nowStamp = timeGen();
if (nowStamp >= lastTimestamp) {
return nowStamp;
}
//如果还出现这个问题,则抛出异常
throw new RuntimeException(String.format("Clock moved backwards again. Refusing to generate id for %d milliseconds", (lastTimestamp - nowStamp)));
}
/**
* 补全字符串位数(最后返回length位)
*
* @param str 原始字符串
* @return java.lang.String
*/
protected String completeDigits(String str, int length) {
if (str.length() >= length) {
return str;
}
int diff = length - str.length() - 1;
//长度标识 + 1位的随机数(可能不存在) + 原递增序列
return this.decimalToSixtyTwo(str.length()) + this.randomChars(diff, false) + str;
}
/**
* 十进制转62进制(仅限正整数)
*
* @param num 十进制数字
* @return java.lang.String
*/
protected String decimalToSixtyTwo(long num) {
if (num <= 0) {
return "0";
}
StringBuilder sb = new StringBuilder();
//余数
long remainder;
while (num > 0) {
remainder = num % 62;
//0-9
if (remainder < 10) {
sb.append((char) ('0' + remainder));
}
//A-Z
else if (remainder < 36) {
sb.append((char) ('A' + remainder - 10));
}
//a-z
else {
sb.append((char) ('a' + remainder - 36));
}
num = num / 62;
}
//因为在上面的循环过程中,后一次循环本应是计算出来的高位字符,但是却被我们放在字符串的最后面,因此最终结果需要再反转一次
return sb.reverse().toString();
}
/**
* 生成指定位数的随机数
*
* @param length 长度
* @param containSpecialChars 是否包含特殊字符
*/
protected String randomChars(int length, boolean containSpecialChars) {
char[] chars = new char[length];
Random rnd = new Random();
//1. 填补空白位置的字符
for (int i = 0; i < length; i++) {
if (chars[i] == 0) {
chars[i] = (containSpecialChars ? nextCharWithSpecialChars(rnd) : nextChar(rnd));
}
}
//2. 返回结果
return new String(chars);
}
/**
* 返回一个随机的字符(包含特殊字符)
*/
private char nextCharWithSpecialChars(Random rnd) {
switch (rnd.nextInt(4)) {
case 0:
return (char) ('a' + rnd.nextInt(26));
case 1:
return (char) ('A' + rnd.nextInt(26));
case 2:
return (char) ('0' + rnd.nextInt(10));
default:
return SPECIAL_CHARS.charAt(rnd.nextInt(SPECIAL_CHARS.length()));
}
}
/**
* 返回一个随机的字符
*/
private char nextChar(Random rnd) {
switch (rnd.nextInt(3)) {
case 0:
return (char) ('a' + rnd.nextInt(26));
case 1:
return (char) ('A' + rnd.nextInt(26));
default:
return (char) ('0' + rnd.nextInt(10));
}
}
}