【问题标题】:Unique Number Generation Algorithm唯一号码生成算法
【发布时间】:2012-05-27 15:08:54
【问题描述】:

我需要为我的 Java 应用程序生成满足以下要求的唯一编号 -

  1. 9位十六进制
  2. 每天大约产生 600,000 个数字
  3. 号码必须保持唯一至少 7 天;如果他们重复超过 7 天,这不是问题。
  4. 在负载高峰期间,需要在大约 15 秒内每秒生成大约 800 个唯一号码。

不成功的解决方案-

    public static String getUniqueId() {
        String uniqueTime = Long.toHexString(System.nanoTime());
        String uniqueId = uniqueTime.substring(uniqueTime.length() - 9);

        return uniqueId;
    }

使用 nanoTime 生成一个 12 位的十六进制数。我截断了左边的 3 个字符。 nanoTime 有助于处理峰值负载。

我认为这是不正确的,可能会导致重复。

谁能提供一个好的快速算法吗?

【问题讨论】:

标签: java algorithm uniqueidentifier


【解决方案1】:

如果只使用一个线程来生成数字:

long nextId = counter % MAX_VALUE;
counter++;
return convertToHex(nextId);

如果是多个线程:

long nextId = atomicLongCounter.getAndIncrement() % MAX_VALUE;
return convertToHex(nextId);

注意:根据@Gumbo 的计算,它需要 313 年才能达到最大值,所以你甚至可以放弃模数。

【讨论】:

    【解决方案2】:

    只使用UUID 怎么样?它们在这种情况下非常有用。 Java implementations available.

    【讨论】:

    • 原则上我会同意。只是发帖人可能有理由特别想要 9 位十六进制数字。
    【解决方案3】:

    简答:加密。由于加密是可逆的,因此您可以保证输入是唯一的,而不是输出是唯一的。使用 36 位块密码(36 位 = 9 个十六进制数字)并加密数字 0、1、2、3、4、...

    您可以在空闲时间预先生成任意数量的数据并存储它们。

    大多数常见的块密码不是 36 位(DES 是 64 位),但 Hasty Pudding Cypher 有 36 位变体,否则您可以使用 RC4 之类的快速流密码或 eSTREAM 密码之一。

    ETA:流密码需要为每个数字重新输入密钥,因此对于您的目的来说可能太慢了。重新设置密钥也会影响唯一性,因为只有在使用相同的密钥时才能保证唯一性。

    【讨论】:

    • 请注意,标准的 SecureRandom 类有效地做到了这一点,因此您无需重新发明轮子。您不需要进行任何花哨的数字预生成:SecureRandom 的预生成实例将在您需要时每秒生成成千上万个数字。但是,一旦添加每个数字为 9 个十六进制数字的条件,就不能保证输出是唯一的。在这个范围内取一个随机数意味着重复很少,但并非不可能,您需要检查它们并在它们出现时生成一个新数字。
    • 例如,使用 SecureRandom 生成随机的 9 位数字将为您提供每周 100 次重复的顺序为提到的数量:检测重复并在每种情况下生成一个新数字是微不足道的,但是您确实需要检查它们。
    • 建议使用 RNG。加密将保证唯一输入的唯一性,否则加密将不可逆。
    • 我想你有点误解了:加密保证了组合的可逆性。它不保证明文>密文本身的唯一映射。因此,例如,在您的流密码中,密码的不同内部状态可能意味着两个不同的输入数字“意外”最终得到相同的输出。但是在解密时,这些又会根据每个相关点的密码内部状态来区分。
    • 你说得对,流密码需要重新加密。这可能会使流密码太慢,以及键控问题。我已经编辑了我的答案。谢谢指正。
    【解决方案4】:

    -并发数生成的另一种可能性

    您最多可以有 10 个单独的线程/进程/机器生成保证不会发生冲突的数字。

    只需使用顺序计数器来生成整数

    start one of them at 0, use increments of 10
    start the next at 1, increments of 10
    start the next at 2, increments of 10
    

    即使上述之一最终承担了全部负载,它也可以持续 7 天而不会溢出 9 位数字,因为在 9 位数字还不够之前,您的序列中有大约 1 亿个,而您每天只能生成 100 万个。

    这里的好处是线程/进程/机器是shared nothing

    顺便说一句-您可以直接以 16 为基数执行此操作-只需使用 16 的增量而不是 10。或者以 5 为基数,以 5 为增量等...

    【讨论】:

      【解决方案5】:

      如果您不需要数字“看起来随机”,那么您可以按照其他人的建议使用计数器。

      我猜您寻找复杂事物的原因是您希望数字“看起来随机”。在这种情况下,您可以执行以下操作:

      • 使用 SecureRandom 的实例来分配您的号码
      • 在内存中保留一个映射到其分配时间的“当前分配”数字的映射(我认为存储在 ConcurrentHashMap 中的这些数据的 600K 数字将是几十兆字节的数据,所以不是很大按照今天的标准处理)
      • 每次您需要分配一个新号码时,只需通过您的 SecureRandom 实例生成 9 个随机十六进制数字(要求它用 5 个随机字节填充一个数组,然后丢弃 4 个位将为您提供 9 个十六进制数字的价值随机数据)
      • 检查您的新号码是否已经在分配号码的地图中。如果是这样,只需循环并生成一个新数字,直到您获得一个唯一的数字 - 实际上,对于您提到的数量,这只会发生每周 100 次左右,因此尽管您确实需要考虑,但不会对性能产生影响为它;
      • 定期扫描分配号码的地图,并清除已分配超过 7 天的号码,以便重新使用。

      对于现实生活中的应用程序,您还需要考虑持久性:每次分配一个数字时,您都需要将其持久化到数据库中,然后再将其返回给客户端,以便在以下情况下可以恢复状态您的服务器崩溃。同样,您的清除操作会先从数据库中删除分配,然后再从内存映射中删除。

      【讨论】:

        【解决方案6】:

        部分出于娱乐目的,请允许我还从您提供的规范中提出另一种可能性。您还可以找到一种伪随机数生成算法,该算法从已知种子开始,在实践中不会针对您每周需要生成的数字数量产生重复。

        例如,以下基于XORShift generator,将产生 600000*7 个无重复的随机 9 位数字:

                long seed = 1;
                for (long i = 1; i <= 600000*7; i++) {
                    long x = seed++;
                    for (int n = 0; n < 3; n++) {
                        x ^= (x << 21);
                        x ^= (x >>> 35);
                        x ^= (x << 4);
                    }
                  x &= 0xfffffffffL;
        
                  // "x" is now the next unique, random-looking number in the sequence
                }
        

        这种方法的优点是你不需要除了计数器之外的存储来确定到目前为止已经生成了哪些数字。

        缺点是每周都以完全相同的顺序重新开始。当然,如果您突然需要增加分配号码的数量,您可能需要找到另一个序列。

        无论如何,我想我会把它混在一起,以防万一有用。

        【讨论】:

          【解决方案7】:

          AtomicLong.incrementAndGet() 应该可以解决问题。

          如果您需要在 JVM 实例之间持久化它,您可能需要分配一个范围,将该范围的最大值存储在数据库或其他事务存储中,并确保在接近最大值时请求一个新范围在AtomicLong 中(加上适当的锁定以确保您不会超出该范围。

          但是,如果您只需要一次运行中的唯一编号,AtomicLong.incrementAndGet 很简单,并且保证在返回到 -1 之前是唯一的,这 1) 在您的一生中不会发生,2) 很容易检查.

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-05-21
            • 1970-01-01
            • 1970-01-01
            • 2010-12-31
            • 1970-01-01
            • 1970-01-01
            • 2012-07-18
            相关资源
            最近更新 更多