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 &lt; 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>&amp; 0xf0) &gt;&gt; 4<span style="color: #000000;">]);
        result.append(digits[b </span>&amp; 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 &lt;br&gt;
    
  •      把时间戳的单位改为分钟,使用25个比特的时间戳(分钟) &lt;br&gt;
    
  •      去掉机器ID和数据中心ID &lt;br&gt; 
    
  •      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 &lt;&lt;<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 &lt;<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 &gt;<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) &amp;<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) &lt;&lt; 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) &lt;&lt; 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 &lt; 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() &amp;<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() &amp;<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>&amp;<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|
posted @   RalphLauren  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示