Java生成唯一ID的三种方法总结

 更新时间:2024年09月21日 10:29:23   作者:长腿大壮  
单机环境下,可以使用AtomicLong来生成唯一ID;而在需要非纯数字形式的场景中,可以通过UUID结合哈希函数如MD5或SHA-1转换成数字,但需注意哈希碰撞的低概率风险;对于分布式系统,模拟Snowflake算法是一种复杂但有效的方法,每种方法都有其适用场景和潜在问题

使用AtomicLong生成唯一ID(适用于单机场景)

这个示例已经在之前的回答中给出,但我会再次展示它,以便与后续示例保持连贯性。

import java.util.concurrent.atomic.AtomicLong;  
  
public class UniqueIdGenerator {  
    private final AtomicLong counter = new AtomicLong(0);  
  
    public long nextId() {  
        return counter.incrementAndGet();  
    }  
  
    public static void main(String[] args) {  
        UniqueIdGenerator generator = new UniqueIdGenerator();  
        long id1 = generator.nextId();  
        long id2 = generator.nextId();  
        System.out.println("ID 1: " + id1);  
        System.out.println("ID 2: " + id2);  
    }  
}

使用UUID并转换为数字形式(虽然不是纯数字,但提供了唯一性)

由于UUID本身是字符串形式的,我们可以通过哈希函数(如MD5、SHA-1等)将其转换为数字,但需要注意哈希碰撞的可能性(尽管在实际应用中非常低)。然而,更常见的是保留UUID的字符串形式或使用其作为基础来生成符合特定需求的数字ID。

import java.util.UUID;  
  
public class UuidToNumberExample {  
    // 注意:这不是将UUID直接转换为唯一数字的有效方法,因为存在哈希碰撞的风险。  
    // 这里只是为了说明概念。  
  
    public static long uuidToNumber(UUID uuid) {  
        // 简单的示例:将UUID的字符串表示形式与另一个数字组合,然后进行哈希(注意:这不是安全的做法)  
        String uuidStr = uuid.toString();  
        long base = 123456789L; // 假设的基数  
        // 这里不实现哈希函数,而是使用字符串长度加基数作为示例(仅为演示)  
        return uuidStr.length() + base; // 这不是一个好的实现!  
  
        // 更实际的方法是使用安全的哈希函数,但请注意哈希碰撞的可能性  
        // 并且,由于哈希函数的输出是固定长度的,直接用作ID可能需要进一步处理  
    }  
  
    public static void main(String[] args) {  
        UUID uuid = UUID.randomUUID();  
        long number = uuidToNumber(uuid); // 这里的实现是错误的,仅用于说明  
        System.out.println("UUID: " + uuid);  
        System.out.println("Converted Number (Incorrect Method): " + number);  
  
        // 正确的做法可能是将UUID用作查找或生成更复杂ID的基础  
    }  
}

模拟Snowflake算法生成唯一ID(分布式场景)

Snowflake算法的实现相对复杂,但我们可以简化其核心思想来展示一个基本框架。

public class SnowflakeIdWorker {  
    // 假设的时间戳、数据中心ID、机器ID和序列号字段(实际应用中需要更精细的管理)  
    private final long twepoch = 1288834974657L; // 自定义起始时间戳  
    private final long datacenterIdBits = 5L; // 数据中心ID位数  
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 数据中心ID最大值  
    private final long workerIdBits = 5L; // 机器ID位数  
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits); // 机器ID最大值  
    private final long sequenceBits = 12L; // 序列号位数  
  
    private final long workerIdShift = sequenceBits; // 机器ID左移位数  
    private final long datacenterIdShift = sequenceBits + workerIdBits; // 数据中心ID左移位数  
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; // 时间戳左移位数  
  
    private long sequence = 0L; // 序列号  
    private long lastTimestamp = -1L; // 上次生成ID的时间戳  
  
    // 构造函数中初始化workerId和datacenterId(这里为示例,实际中应由外部指定)  
    public SnowflakeIdWorker(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("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));  
        }  
        // 这里省略了实际将workerId和datacenterId设置到对象中的步骤  
    }  
  
    // 生成ID的方法(简化版,未包含时钟回拨处理等)  
    public synchronized long nextId() {  
        long timestamp = timeGen();  
  
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回拨过  
        if (timestamp < lastTimestamp) {  
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));  
        }  
  
        // 如果是同一时间生成的,则进行毫秒内序列  
        if (lastTimestamp == timestamp) {  
            sequence = (sequence + 1) & (-1L ^ (-1L << sequenceBits));  
            if (sequence == 0) {  
                // 毫秒内序列溢出  
                timestamp = tilNextMillis(lastTimestamp);  
            }  
        } else {  
            // 时间戳改变,毫秒内序列重置  
            sequence = 0L;  
        }  
  
        // 上次生成ID的时间戳  
        lastTimestamp = timestamp;  
  
        // 移位并通过或运算拼到一起组成64位的ID  
        // 这里省略了实际的workerId和datacenterId的左移和或操作  
        // 示例:return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;  
        return 0L; // 返回模拟的0,实际应返回通过上述方式计算的ID  
    }  
  
    // 模拟获取系统当前时间戳(毫秒)  
    protected long timeGen() {  
        return System.currentTimeMillis();  
    }  
  
    // 等待到下一个毫秒,获得新的时间戳  
    protected long tilNextMillis(long lastTimestamp) {  
        long timestamp = timeGen();  
        while (timestamp <= lastTimestamp) {  
            timestamp = timeGen();  
        }  
        return timestamp;  
    }  
  
    // 省略main方法和其他可能的辅助方法...  
}

请注意,上述Snowflake算法的实现是高度简化的,并省略了许多关键细节(如实际的数据中心ID和机器ID的设置、序列号的正确处理以及时钟回拨的详细处理逻辑)。在实际应用中,您需要根据自己的需求和环境来完整实现这些功能。

总结

在单机环境下,可以使用AtomicLong来生成唯一ID;而在需要非纯数字形式的场景中,可以通过UUID结合哈希函数如MD5或SHA-1转换成数字,但需注意哈希碰撞的低概率风险;对于分布式系统,模拟Snowflake算法是一种复杂但有效的方法,每种方法都有其适用场景和潜在问题,需要根据具体需求和环境选择合适的实现方式。

到此这篇关于Java生成唯一ID的三种方法总结的文章就介绍到这了,更多相关Java生成唯一ID内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java通过匿名类来实现回调函数实例总结

    Java通过匿名类来实现回调函数实例总结

    这篇文章主要介绍了Java通过匿名类来实现回调函数的例子,回调函数就是一种函数签名(若干个输入参数、一个输出参数)的规范,java虽不存在函数声明,但是java可以用接口来强制规范。具体操作步骤大家可查看下文的详细讲解,感兴趣的小伙伴们可以参考一下。
    2017-08-08
  • java版十大排序经典算法:完整代码

    java版十大排序经典算法:完整代码

    优秀的文章也不少,但是Java完整版的好像不多,我把所有的写一遍巩固下,同时也真诚的希望阅读到这篇文章的小伙伴们可以自己去从头敲一遍,不要粘贴复制!希望我的文章对你有所帮助,每天进步一点点
    2021-07-07
  • 深入浅出讲解Spring框架中AOP及动态代理的应用

    深入浅出讲解Spring框架中AOP及动态代理的应用

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
    2022-03-03
  • Java中实现OCR识别读取图片中的文字

    Java中实现OCR识别读取图片中的文字

    图片内容一般无法编辑,如果想要读取图片中的文本,我们需要用到OCR工具,本文将介绍如何在Java中实现OCR识别读取图片中的文字,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-04-04
  • springboot实现分页功能的完整代码

    springboot实现分页功能的完整代码

    Spring Boot是一个快速开发框架,它提供了很多便捷的功能,其中包括分页查询,下面这篇文章主要给大家介绍了关于springboot实现分页功能的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Spring的同一个服务会加载多次的问题分析及解决方法

    Spring的同一个服务会加载多次的问题分析及解决方法

    这篇文章主要介绍了Spring的同一个服务为什么会加载多次,我们先来梳理一下 Web 容器中如何加载 Bean,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-10-10
  • java的Builder原理和实现详解

    java的Builder原理和实现详解

    大家好,本篇文章主要讲的是java的Builder原理和实现详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • SpringIOC框架的简单实现步骤

    SpringIOC框架的简单实现步骤

    这篇文章主要介绍了SpringIOC框架简单实现步骤,帮助大家更好的理解和学习使用Spring,感兴趣的朋友可以了解下
    2021-05-05
  • Java如何接收前端easyui datagrid传递的数组参数

    Java如何接收前端easyui datagrid传递的数组参数

    这篇文章分享一下怎么在easyui的datagrid刷新表格时,在后端java代码中接收datagrid传递的数组参数,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2023-11-11
  • 详解Java拦截器以及自定义注解的使用

    详解Java拦截器以及自定义注解的使用

    这篇文章主要为大家介绍了Java拦截器以及自定义注解的使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助<BR>
    2021-12-12

最新评论