首页 > 解决方案 > 如何在 java 中生成 5 个字符的唯一字母数字值?

问题描述

我为一个银行项目工作,他们的要求是为每笔交易生成唯一的交易参考。UTR 的格式为:

<BankCode><YYDDD><5 位 SequenceId>

这个 5 位数的序列 ID 也可以是字母数字。每天的交易数量可以达到 100-200K。

如果我使用 Oracle 序列,那么我只能有 10K 个值。

我尝试使用SecureRandom生成器并生成 200K 5 长度的字符串,但它生成了大约 30 个重复的字符串。

下面是我使用的代码片段

int leftLimit = 48;
int rightLimit = 122;
int i1=0;
Random random = new SecureRandom();
while (i1<200000) {
    String generatedString = random.ints(leftLimit, rightLimit+1)
                                   .filter(i -> (i<=57||i>=65) && ( i<=90|| i>=97))
                                   .limit(5)
                                   .collect(StringBuilder::new,
                                            StringBuilder::appendCodePoint,
                                            StringBuilder::append)
                                   .toString();
    System.out.println(generatedString);
    i1++;
}

标签: javauniqueidentifier

解决方案


如果你想要一个伪随机序列,我建议你使用自定义的Feistel实现。Feistel 被设计为一种互惠机制,因此您可以通过重新应用它来解码 Feistel,这意味着i == feistel(feistel(i))如果您从 1 到 X,您将获得 1 和 X 之间的所有数字恰好一次。所以没有碰撞。

基本上,您可以使用 36 个字符。因此,您有 60,466,176 个可能的值,但您只需要其中的 200,000 个。但实际上,我们不在乎你想要多少,因为 Feistel 确保没有碰撞。

您会注意到二进制中的 60,466,176 是0b11100110101010010000000000,这是一个 26 位的数字。26 对代码不是很友好,所以我们将自定义 feistel 映射器包装成 24 位。Feistel 必须将一个数字分成两个偶数部分,每个部分都是 12 位。这只是为了解释您将在下面的代码中看到的值,如果您查看其他实现,它是 12 而不是 16。此外,0xFFF是 12 位的掩码。

现在算法本身:

  static int feistel24bits(int value) {
    int l1 = (value >> 12) & 0xFFF;
    int r1 = value & 0xFFF;
    for (int i = 0; i < 3; i++) {
      int key = (int)((((1366 * r1 + 150889) % 714025) / 714025d) * 0xFFF);
      int l2 = r1;
      int r2 = l1 ^ key;
      l1 = l2;
      r1 = r2;
    }
    return (r1 << 12) | l1;
  } 

所以基本上,这意味着如果你给这个算法一个介于016777215( = 2 24 -1) 之间的数字,你将得到一个唯一的伪随机数,当用 base-36 编写时,它可以容纳在 5 个字符串中。

那么如何让这一切正常工作呢?嗯,这很简单:

String nextId() {
  int sequence = (retrieveSequence() + 1) & 0xFFFFFF; // bound to 24 bits
  int nextId = feistel24bits(sequence);
  storeSequence(sequence);
  return intIdToString(nextId);
}
static String intIdToString(int id) {
  String str = Integer.toString(id, 36);
  while(str.length() < 5) { str = "0" + str; }
  return str;
}

这是我使用的完整代码。


推荐阅读