Java中生成唯一ID的方法
Java中生成唯一ID的方法| Id | Title | DateAdded | SourceUrl | PostType | Body | BlogId | Description | DateUpdated | IsMarkdown | EntryName | CreatedTime | IsActive | AutoDesc | AccessPermission |
| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------| -------------|
| 14298610| Java中生成唯一ID的方法| 2021-01-19T16:24:00| | BlogPost|
有时我们不依赖于数据库中自动递增的字段产生唯一ID,比如多表同一字段需要统一一个唯一ID,这时就需要用程序来生成一个唯一的全局ID。
UUID
从Java 5开始, UUID 类提供了一种生成唯一ID的简单方法。UUID是通用唯一识别码 (Universally Unique Identifier)的缩写,UUID来源于OSF(Open Software Foundation,开源软件基金会)的DCE(Distributed Computing Environment,分布式计算环境)规范。UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。如此一来,每个人都可以建立不与其它人冲突的 UUID。
UUID是一个128bit的数字,也可以表现为32个16进制的字符(每个字符0-F的字符代表4bit),中间用"-"分割。
时间戳+UUID版本号: 分三段占16个字符(60bit+4bit),
Clock Sequence号与保留字段:占4个字符(13bit+3bit),
节点标识:占12个字符(48bit),
UUID的唯一缺陷在于生成的结果串会比较长。
public class GenerateUUID {
public static final void main(String... args) {
// generate random UUIDs
UUID idOne = UUID.randomUUID();
UUID idTwo = UUID.randomUUID();
log("UUID One: " + idOne);
log("UUID Two: " + idTwo);
}
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> log(Object object) {
System.out.println(String.valueOf(object));
}
}
结果为
UUID One: 6b193443-b95d-4462-9902-a6455ebc56d6
UUID Two: 4ef9b375-839b-4150-8f31-1ed85fab63fd
随机数的哈希值
此方法使用SecureRandom和MessageDigest:
启动时,初始化SecureRandom (这可能是一个冗长的操作)
使用 SecureRandom生成一个随机数
创建一个MessageDigest,使用某种摘要算法
将MessageDigest返回的byte[]编码为某种可接受的文本形式
检查结果是否已经被使用;如果尚未使用,则适合作为唯一标识符
MessageDigest类是适合于产生任意数据的“单向散列”。
public class GenerateId {
public static void main(String... arguments) {
try {
SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
String randomNum </span>=<span style="color: #000000;"> Integer.valueOf(prng.nextInt()).toString();
MessageDigest sha </span>= MessageDigest.getInstance("SHA-1"<span style="color: #000000;">);
</span><span style="color: #0000ff;">byte</span>[] result =<span style="color: #000000;"> sha.digest(randomNum.getBytes());
System.out.println(</span>"Random number: " +<span style="color: #000000;"> randomNum);
System.out.println(</span>"Message digest: " +<span style="color: #000000;"> hexEncode(result));
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (NoSuchAlgorithmException ex) {
System.err.println(ex);
}
}
</span><span style="color: #0000ff;">static</span> <span style="color: #0000ff;">private</span> String hexEncode(<span style="color: #0000ff;">byte</span><span style="color: #000000;">[] input) {
StringBuilder result </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuilder();
</span><span style="color: #0000ff;">char</span>[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a'<span style="color: #000000;">,
</span>'b', 'c', 'd', 'e', 'f'<span style="color: #000000;">};
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> idx = 0; idx < input.length; ++<span style="color: #000000;">idx) {
</span><span style="color: #0000ff;">byte</span> b =<span style="color: #000000;"> input[idx];
result.append(digits[(b </span>& 0xf0) >> 4<span style="color: #000000;">]);
result.append(digits[b </span>& 0x0f<span style="color: #000000;">]);
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> result.toString();
}
}
结果为
Random number: -2017013782
Message digest: 2c3bba8d4dbd3699648c5909685d21f9c64b6a8a
Twitter的snowflake
twitter的一个全局唯一id生成器,结果是一个long型的ID。
正数位(1bit):一个符号位,永远是0。
时间戳(41bit) :自从2012年以来的毫秒数,能撑139年。
自增序列(12bit,最大值4096):毫秒之内的自增,过了一毫秒会重新置0。
DataCenter ID (5 bit, 最大值32):配置值,支持多机房。
Worker ID ( 5 bit, 最大值32),配置值,一个机房里最多32个机器。
Snowflake算法的变化
Snowflake算法生成的唯一ID为long型数值,但如果想在应用中使用int类型的自增ID的话可以做些调整。
时间戳改为分钟(25bit),自增序列(7bit)。自增序列最大值128,在一分钟内会不够使用。可以采用预支方式取下一分钟。
此方式只适用于一个单体应用,不适合分布式系统。
/**
- @ClassName: SnowflakeIdWorker3rd
- @Description:snowflake算法改进
- @author: wanghao
- @date: 2019年12月13日 下午12:50:47
- @version V1.0
-
将产生的Id类型更改为Integer 32bit <br>
-
把时间戳的单位改为分钟,使用25个比特的时间戳(分钟) <br>
-
去掉机器ID和数据中心ID <br>
-
7个比特作为自增值,即2的7次方等于128。
/
public class SnowflakeIdWorker3rd {
/** 开始时间戳 (2019-01-01) /
private final int twepoch = 25771200;// 1546272000000L/1000/60;
<span style="color: #008000;">/**</span><span style="color: #008000;"> 序列在id中占的位数 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">long</span> sequenceBits = 7L<span style="color: #000000;">;
</span><span style="color: #008000;">/**</span><span style="color: #008000;"> 时间截向左移7位 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">long</span> timestampLeftShift =<span style="color: #000000;"> sequenceBits;
</span><span style="color: #008000;">/**</span><span style="color: #008000;"> 生成序列的掩码,这里为127 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> sequenceMask = -1 ^ (-1 <<<span style="color: #000000;"> sequenceBits);
</span><span style="color: #008000;">/**</span><span style="color: #008000;"> 分钟内序列(0~127) </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">int</span> sequence = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">int</span> laterSequence = 0<span style="color: #000000;">;
</span><span style="color: #008000;">/**</span><span style="color: #008000;"> 上次生成ID的时间戳 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">int</span> lastTimestamp = -1<span style="color: #000000;">;
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">final</span> MinuteCounter counter = <span style="color: #0000ff;">new</span><span style="color: #000000;"> MinuteCounter();
</span><span style="color: #008000;">/**</span><span style="color: #008000;"> 预支时间标志位 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">boolean</span> isAdvance = <span style="color: #0000ff;">false</span><span style="color: #000000;">;
</span><span style="color: #008000;">//</span><span style="color: #008000;"> ==============================Constructors=====================================</span>
<span style="color: #0000ff;">public</span><span style="color: #000000;"> SnowflakeIdWorker3rd() {
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> ==============================Methods==========================================</span>
<span style="color: #008000;">/**</span><span style="color: #008000;">
* 获得下一个ID (该方法是线程安全的)
*
* </span><span style="color: #808080;">@return</span><span style="color: #008000;"> SnowflakeId
</span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> nextId() {
</span><span style="color: #0000ff;">int</span> timestamp =<span style="color: #000000;"> timeGen();
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常</span>
<span style="color: #0000ff;">if</span> (timestamp <<span style="color: #000000;"> lastTimestamp) {
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span><span style="color: #000000;"> RuntimeException(String.format(
</span>"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp -<span style="color: #000000;"> timestamp));
}
</span><span style="color: #0000ff;">if</span>(timestamp ><span style="color: #000000;"> counter.get()) {
counter.set(timestamp);
isAdvance </span>= <span style="color: #0000ff;">false</span><span style="color: #000000;">;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 如果是同一时间生成的,则进行分钟内序列</span>
<span style="color: #0000ff;">if</span> (lastTimestamp == timestamp ||<span style="color: #000000;"> isAdvance) {
</span><span style="color: #0000ff;">if</span>(!<span style="color: #000000;">isAdvance) {
sequence </span>= (sequence + 1) &<span style="color: #000000;"> sequenceMask;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 分钟内自增列溢出</span>
<span style="color: #0000ff;">if</span> (sequence == 0<span style="color: #000000;">) {
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 预支下一个分钟,获得新的时间戳</span>
isAdvance = <span style="color: #0000ff;">true</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> laterTimestamp =<span style="color: #000000;"> counter.get();
</span><span style="color: #0000ff;">if</span> (laterSequence == 0<span style="color: #000000;">) {
laterTimestamp </span>=<span style="color: #000000;"> counter.incrementAndGet();
}
</span><span style="color: #0000ff;">int</span> nextId = ((laterTimestamp - twepoch) << timestampLeftShift) <span style="color: #008000;">//
| laterSequence;
laterSequence = (laterSequence + 1) & sequenceMask;
return nextId;
}
}
// 时间戳改变,分钟内序列重置
else {
sequence = 0;
laterSequence = 0;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 上次生成ID的时间截</span>
lastTimestamp =<span style="color: #000000;"> timestamp;
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 移位并通过或运算拼到一起组成32位的ID</span>
<span style="color: #0000ff;">return</span> ((timestamp - twepoch) << timestampLeftShift) <span style="color: #008000;">//
| sequence;
}
</span><span style="color: #008000;">/**</span><span style="color: #008000;">
* 返回以分钟为单位的当前时间
*
* </span><span style="color: #808080;">@return</span><span style="color: #008000;"> 当前时间(分钟)
</span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> timeGen() {
String timestamp </span>= String.valueOf(System.currentTimeMillis() / 1000 / 60<span style="color: #000000;">);
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> Integer.valueOf(timestamp);
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> ==============================Test=============================================</span>
<span style="color: #008000;">/**</span><span style="color: #008000;"> 测试 </span><span style="color: #008000;">*/</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
SnowflakeIdWorker3rd idWorker </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> SnowflakeIdWorker3rd();
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < 1000; i++<span style="color: #000000;">) {
</span><span style="color: #0000ff;">long</span> id =<span style="color: #000000;"> idWorker.nextId();
System.out.println(i </span>+ ": " +<span style="color: #000000;"> id);
}
}
}
public class MinuteCounter {
private static final int MASK = 0x7FFFFFFF;
private final AtomicInteger atom;
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> MinuteCounter() {
atom </span>= <span style="color: #0000ff;">new</span> AtomicInteger(0<span style="color: #000000;">);
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> incrementAndGet() {
</span><span style="color: #0000ff;">return</span> atom.incrementAndGet() &<span style="color: #000000;"> MASK;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> get() {
</span><span style="color: #0000ff;">return</span> atom.get() &<span style="color: #000000;"> MASK;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> set(<span style="color: #0000ff;">int</span><span style="color: #000000;"> newValue) {
atom.set(newValue </span>&<span style="color: #000000;"> MASK);
}
}
7人点赞
随笔
作者:RaiseHead
链接:https://www.jianshu.com/p/b88a48e315de
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
| 648658| | 2024-04-29T20:45:00| false| | 2021-01-19T16:24:18.993| true| 有时我们不依赖于数据库中自动递增的字段产生唯一ID,比如多表同一字段需要统一一个唯一ID,这时就需要用程序来生成一个唯一的全局ID。 UUID 从Java 5开始, UUID 类提供了一种生成唯一ID的简单方法。UUID是通用唯一识别码 (Universally Unique Identifier)| Anonymous|
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现