使用Java生成永不重复的数字的实现方案

 更新时间:2025年06月09日 10:55:54   作者:喵手  
在现代应用开发中,一个常见的需求是生成 永不重复的数字,无论是在订单系统中生成唯一订单号,还是分布式系统中生成唯一标识,生成不重复的数字或ID都是至关重要的,本期我们将关注 Java 生产永不重复的数字,通过多个角度剖析不同场景下的解决方案,需要的朋友可以参考下

摘要

本文以 Java 实现生成永不重复的数字 为核心,详细介绍了几种不同的实现方法,包括简单的自增算法、基于时间戳的生成方式、UUID 的使用,以及在分布式系统中常见的雪花算法。每种方法都有其适用的场景和优势。通过源码解析、实际使用案例分享和测试用例,我们将探讨如何在不同场景下生成唯一且不重复的数字或标识符,并分析各方法的优缺点,帮助开发者选择适合自己业务的最佳方案。

概述

在现代应用中,生成唯一且不重复的数字是一项关键任务,尤其是在分布式系统和多线程环境中。例如:

  • 电商系统中生成唯一订单号
  • 社交网络中为用户生成唯一的ID
  • 分布式数据库中生成唯一的主键

常见的生成方式

  • 自增数字:最简单的生成唯一数字的方式,即通过一个全局递增的数字生成器。
  • 时间戳结合随机数:通过系统当前时间(时间戳)加上随机数来生成不重复的数字。
  • UUID:Java 自带的 UUID 类,能够生成几乎保证全局唯一的标识符。
  • 雪花算法(Snowflake):Twitter 提出的分布式系统中生成全局唯一ID的算法。

每种方式都有不同的使用场景,我们将逐一分析。

源码解析

1. 自增数字生成器

最简单的方式是使用自增数字,通过维护一个全局变量,每次生成一个数字时,将其自增。对于单线程环境或简单的需求场景,这种方式非常有效。

public class IncrementalNumberGenerator {
    private static long currentNumber = 0;

    // 线程安全的自增方法
    public static synchronized long getNextNumber() {
        return ++currentNumber;
    }
}

代码解析:

  • currentNumber 作为静态变量,存储当前的数字。
  • getNextNumber 方法使用 synchronized 关键字确保线程安全,在并发环境下防止多线程同时修改 currentNumber 的问题。

2. 时间戳结合随机数生成

时间戳(毫秒级)结合随机数生成唯一数字的方式较为常见,能够在较大范围内保证唯一性。

import java.util.Random;

public class TimestampRandomNumberGenerator {
    private static final Random random = new Random();

    public static String generateUniqueNumber() {
        long timestamp = System.currentTimeMillis();
        int randomNumber = random.nextInt(1000); // 随机生成0-999的数字
        return timestamp + String.format("%03d", randomNumber); // 拼接时间戳和随机数
    }
}

代码解析:

  • System.currentTimeMillis() 获取当前时间戳(单位:毫秒)。
  • 使用 Random 类生成一个三位随机数。
  • 将时间戳和随机数拼接成一个字符串,保证唯一性。

3. UUID 生成

import java.util.UUID;

public class UUIDGenerator {
    public static String generateUUID() {
        return UUID.randomUUID().toString();
    }
}

代码解析:

  • UUID.randomUUID() 生成一个随机的 UUID。
  • UUID 通常由32个字符组成,包含字母和数字,格式如 550e8400-e29b-41d4-a716-446655440000

4. 雪花算法(Snowflake)

雪花算法是一种分布式环境下生成唯一ID的算法,由 Twitter 提出,它能够在分布式系统中生成64位的全局唯一ID。其ID由时间戳、机器ID和序列号组成,能保证在高并发情况下生成不重复的数字。

public class SnowflakeIdGenerator {
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(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));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
               (datacenterId << datacenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}

代码解析:

  • 时间戳:用于确保生成的ID按照时间顺序递增。
  • 机器ID和数据中心ID:用于在分布式系统中标识不同的机器和数据中心,防止ID冲突。
  • 序列号:在同一毫秒内生成多个ID时,用于区分这些ID。

雪花算法生成的ID是一个64位长的整数,能够在分布式环境下保证唯一性,且生成速度非常快。

使用案例分享

案例 1:基于自增数字生成订单号

对于中小型电商平台,生成唯一订单号的方式可以通过自增数字结合业务标识来完成。如下所示:

public class OrderService {
    private static long orderId = 0;

    public synchronized static String generateOrderNumber() {
        return "ORDER" + (++orderId);
    }
}

案例 2:分布式系统中的唯一标识生成

对于分布式系统,雪花算法是一种常见的解决方案。下面是一个分布式用户ID生成的示例:

public class UserIdGenerator {
    private static final SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1); // 假设机器ID和数据中心ID为1

    public static long generateUserId() {
        return idGenerator.nextId();
    }
}

应用场景案例

  • 订单号生成:在电商系统中,需要为每个订单生成唯一的订单号,避免重复的订单处理和数据混乱。
  • 分布式系统中的唯一标识生成:在分布式架构中,多个节点同时进行任务时,生成全局唯一的ID是保障数据

一致性的关键。

优缺点分析

自增数字

  • 优点:实现简单,易于管理。
  • 缺点:仅适用于单机环境,多线程环境下需要同步处理,且不适合分布式系统。

时间戳结合随机数

  • 优点:能够在大多数场景下保证唯一性,生成速度较快。
  • 缺点:在高并发环境下有可能出现重复,随机数的范围较小。

UUID

  • 优点:能够生成几乎全局唯一的标识,且使用简单。
  • 缺点:UUID较长,不适合需要短ID的场景。

雪花算法

  • 优点:适合分布式环境,能够保证生成ID的唯一性和有序性。
  • 缺点:实现较为复杂,需要合理配置机器ID和数据中心ID。

核心类方法介绍

ystem.currentTimeMillis()

返回当前时间的毫秒数,自1970年1月1日开始计算。

Random.nextInt(int bound)

生成一个在 [0, bound) 范围内的随机整数。

UUID.randomUUID()

生成一个128位的随机UUID。

SnowflakeIdGenerator.nextId()

生成一个唯一的64位ID,用于分布式环境下的唯一标识生成。

测试用例

用例1:测试自增数字生成

@Test
public void testIncrementalNumberGeneration() {
    long num1 = IncrementalNumberGenerator.getNextNumber();
    long num2 = IncrementalNumberGenerator.getNextNumber();
    assertNotEquals(num1, num2);
}

代码解析:

如下是具体的代码解析,希望对大家有所帮助:

这段Java代码定义了一个测试方法 testIncrementalNumberGeneration,用于测试增量数字生成器是否能够生成不同的连续数字。

下面是这段代码的详细解读:

  • @Test:这是一个JUnit注解,表示接下来的方法是测试方法。
  • public void testIncrementalNumberGeneration() { ... }:定义了一个名为 testIncrementalNumberGeneration 的测试方法。
  • long num1 = IncrementalNumberGenerator.getNextNumber();:调用 IncrementalNumberGenerator 类的静态方法 getNextNumber 来生成第一个数字,并将其存储在变量 num1 中。
  • long num2 = IncrementalNumberGenerator.getNextNumber();:再次调用 getNextNumber 方法生成第二个数字,并将其存储在变量 num2 中。
  • assertNotEquals(num1, num2);:使用 assertNotEquals 断言方法来验证 num1 和 num2 是否不同。如果两个数字不相同,测试将通过;如果相同,则测试将失败。

总结:这个测试用例的目的是验证增量数字生成器生成的两个连续数字是否不相同。增量数字生成器通常用于确保每个生成的数字都是唯一的,并且每个后续数字都比前一个大,这在生成序列号、版本号等时非常有用。

注意:代码中假设 IncrementalNumberGenerator 类已经定义,并且它的 getNextNumber 方法能够生成连续的数字。此外,测试方法的名称表明它专注于数字生成器的功能,确保每次调用 getNextNumber 方法都能得到一个更大的数字。如果 IncrementalNumberGenerator 是多线程安全的,那么即使在并发环境下,这个测试也应该能够通过。

用例2:测试雪花算法生成唯一ID

@Test
public void testSnowflakeIdGeneration() {
    SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1);
    long id1 = generator.nextId();
    long id2 = generator.nextId();
    assertNotEquals(id1, id2);
}

代码解析:

如下是具体的代码解析,希望对大家有所帮助:

这段Java代码定义了一个测试方法 testSnowflakeIdGeneration,用于测试雪花算法(Snowflake Algorithm)ID生成器是否能够生成不同的ID。

下面是这段代码的详细解读:

  • @Test:这是一个JUnit注解,表示接下来的方法是测试方法。
  • public void testSnowflakeIdGeneration() { ... }:定义了一个名为 testSnowflakeIdGeneration 的测试方法。
  • SnowflakeIdGenerator generator = new SnowflakeIdGenerator(1, 1);:创建了 SnowflakeIdGenerator 类的一个实例,这个类可能是一个实现了Twitter雪花算法的ID生成器。它的构造函数接受两个参数,通常表示数据中心ID和机器ID。
  • long id1 = generator.nextId();:调用 generator 实例的 nextId 方法生成第一个ID,并将其存储在变量 id1 中。
  • long id2 = generator.nextId();:再次调用 nextId 方法生成第二个ID,并将其存储在变量 id2 中。
  • assertNotEquals(id1, id2);:使用 assertNotEquals 断言方法来验证 id1 和 id2 是否不同。如果两个ID不相同,测试将通过;如果相同,则测试将失败。

总结:这个测试用例的目的是验证ID生成器生成的两个连续ID是否不相同。雪花算法ID生成器通常用于分布式系统中生成唯一的ID,它结合了时间戳、数据中心ID和机器ID来确保生成的ID的唯一性。

小结

本文通过多种方案介绍了如何在 Java 中生成永不重复的数字。从简单的自增数字到适用于分布式环境的雪花算法,各种方案适用于不同的场景。对于单机环境,简单的自增数字或时间戳结合随机数足够使用,而在分布式环境下,雪花算法则成为了最佳选择。

总结

Java 生成不重复数字的方案多种多样,开发者需要根据具体的应用场景选择最合适的方案。本文从单机环境到分布式系统,依次分析了自增、时间戳结合随机数、UUID和雪花算法,并提供了相关代码和案例。掌握这些方案,可以帮助开发者在实际项目中应对不同的唯一标识生成需求,保证系统的稳定性和数据的一致性。

以上就是使用Java生成永不重复的数字的实现方案的详细内容,更多关于Java生成永不重复数字的资料请关注脚本之家其它相关文章!

相关文章

  • 浅析Java BigDecimal为什么可以不丢失精度

    浅析Java BigDecimal为什么可以不丢失精度

    在金融领域,为了保证数据的精度,往往会使用BigDecimal,所以这篇文章主要来和大家探讨下为什么BigDecimal可以保证精度不丢失,感兴趣的可以了解下
    2024-03-03
  • Java动态代理机制详解_动力节点Java学院整理

    Java动态代理机制详解_动力节点Java学院整理

    这篇文章主要为大家详细介绍了Java动态代理机制,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java 使用反射调用jar包中的类方式

    Java 使用反射调用jar包中的类方式

    这篇文章主要介绍了Java 使用反射调用jar包中的类方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Servlet3.0学习总结之基于Servlet3.0的文件上传实例

    Servlet3.0学习总结之基于Servlet3.0的文件上传实例

    本篇文章主要介绍了Servlet3.0学习总结之基于Servlet3.0的文件上传实例,具有一定的参考价值,有兴趣的可以了解一下
    2017-07-07
  • 解决Java执行Cmd命令出现的死锁问题

    解决Java执行Cmd命令出现的死锁问题

    这篇文章主要介绍了关于Java执行Cmd命令出现的死锁问题解决,解决方法就是在waitfor()方法之前读出窗口的标准输出、输出、错误缓冲区中的内容,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Java中==和equals()的区别总结

    Java中==和equals()的区别总结

    ==和equals是我们面试中经常会碰到的问题,那么它们之间有什么联系和区别呢?这篇文章主要给大家介绍了关于Java中==和equals()区别的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • springboot docker jenkins 自动化部署并上传镜像的步骤详解

    springboot docker jenkins 自动化部署并上传镜像的步骤详解

    这篇文章主要介绍了springboot docker jenkins 自动化部署并上传镜像的相关资料,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • java实现简单超市管理系统

    java实现简单超市管理系统

    这篇文章主要为大家详细介绍了java实现简单超市管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • idea添加数据库图文教程

    idea添加数据库图文教程

    这篇文章主要介绍了idea添加数据库图文教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java日志组件间关系详解

    Java日志组件间关系详解

    在本文里我们给大家整理了关于Java日志组件间关系相关基础知识,需要的朋友们跟着学习下。
    2019-02-02

最新评论