固定长度的唯一标识创建

4

我一直在寻找在Java代码中生成UID的方法(大多数都是在stackoverflow上找到的)。最好的方法是使用Java的UUID创建唯一标识符,因为它使用时间戳。但我的问题是它长度为128位,我需要一个更短的字符串,比如说14或15个字符。因此,我设计了以下代码来实现。

Date date = new Date();
Long luid = (Long) date.getTime();
String suid = luid.toString();
System.out.println(suid+": "+suid.length() + " characters");

Random rn = new Random();
Integer long1 = rn.nextInt(9);
Integer long2 = rn.nextInt(13);

String newstr = suid.substring(0, long2) + " " + long1 + " " + suid.subtring(long2);
System.out.println("New string in spaced format: "+newstr);
System.out.println("New string in proper format: "+newstr.replaceAll(" ", ""));

请注意,我只是展示经过空格格式化和正确格式化的字符串,与原始字符串进行比较。
这种方法能够保证每次都生成100%独一无二的ID吗?或者您是否认为数字会重复出现?另外,我可以将随机数插入到开头或结尾而不是随机位置,这样可以避免可能导致重复数字的问题。这是为了完成所需长度的UID。但是,如果您需要少于13个字符的UID,则这种方法可能不起作用。
您有什么想法吗?

当然,没有任何保证 任何 ID 是唯一的 -- 毕竟,ID 的集合是有限的,而且你可以随意查看任意数量的ID。(当然,对于128位UUID也是如此。) - Vlad
但是时间戳总是唯一的,除非您在几分之一秒内进行了两个请求,这种情况很少发生。这与随机数一起使用应该增加始终获取唯一值的概率。 - Rishi P
好的,我的意思并不是“时间戳”这个词的确切含义。它是Date.getTime(),表示毫秒数。这个数字不会在达到上限后重复,对吧?(可能是个愚蠢的问题 :P) - Rishi P
嗯,我意识到这并不像我想象的那样聪明 :( getTime() 函数依赖于您的机器/服务器时间。所以我想这和其他任何 UID 一样容易出错。最后,我认为我会按照通常的方式 - 将 UID 存储在数据库中,并在创建新 UID 时进行检查。 - Rishi P
我的第一个问题是:你是否从多个进程中生成这些ID?如果不是,那么一个简单的单调递增序列就足够了。根据@Vlad的研究,long类型应该足够大了。UUID是为了允许在任何时间从任何机器生成高概率的唯一ID而创建的。 - sceaj
显示剩余8条评论
2个回答

3
如果这是一个分布式系统,那么以下方法就不适用了,但是如下方案可行:
private AtomicLong uniqueId = new AtomicLong(0);
...
// get a unique current-time-millis value 
long now;
long prev;
do {
    prev = uniqueId.get();
    now = System.currentTimeMillis();
    // make sure now is moving ahead and unique
    if (now <= prev) {
        now = prev + 1;
    }
    // loop if someone else has updated the id
} while (!uniqueId.compareAndSet(prev, now));

// shuffle it
long result = shuffleBits(now);
System.out.println("Result is " + Long.toHexString(result));

public static long shuffleBits(long val) {
    long result = 0;
    result |= (val & 0xFF00000000000000L) >> 56;
    result |= (val & 0x00FF000000000000L) >> 40;
    result |= (val & 0x0000FF0000000000L) >> 24;
    result |= (val & 0x000000FF00000000L) >> 8;
    result |= (val & 0x00000000FF000000L) << 8;
    result |= (val & 0x0000000000FF0000L) << 24;
    result |= (val & 0x000000000000FF00L) << 40;
    result |= (val & 0x00000000000000FFL) << 56;
    return result;
}

位操作可以改进,以在每次迭代中生成更多值的变化。您提到不希望数字是连续的,但并未指定完全随机性的要求。
当然,这不如UUID好,但比数据库操作更快。

1

如果数据库支持序列的话,最简单的方法是使用数据库序列。 如果不支持,您可以按照以下方式模拟它们:

  1. 创建一个包含一列的表,该列将保存到目前为止使用的最大值(最初为0)。有些应用程序创建多行,其中每行控制特定的唯一ID,但实际上只需要一行。对于此示例,请假设表结构如下:

    ID_TABLE
    ID_NAME    VARCHAR(40); -- 或者适当的其他类型
    ID_COLUMN  INTEGER; -- 或者适当的其他类型
    
  2. 每个进程通过执行以下操作来保留行:

    a. 开始事务;
    b. 更新ID_TABLE,设置ID_VALUE = ID_VALUE + <n>,其中ID_NAME = <name>;
    c. 从ID_TABLE中选择ID_VALUE,其中ID_NAME = <name>;
    d. 提交事务;
    

    如果所有操作都成功完成,则刚刚保留了范围(val - n + 1)到val,其中val是步骤c中返回的值。

  3. 每个进程从其保留的范围中分配ID。如果进程是多线程的,则必须提供同步以确保每个值最多只分配一次。当它耗尽其价值供应时,它返回到步骤2并保留更多价值。请注意,并非保留的所有值都保证被使用。如果进程终止而没有使用其保留的所有值,则未使用的值将丢失并永远不会使用。


是的,这很好。但问题是所有的ID现在看起来都非常顺序,这是我忘了提到我不想要的。我希望它一开始看起来有点随机,时间戳看起来确实像。感谢您的帮助。 - Rishi P
如果您的问题尽可能精确,将有助于您获得良好的答案。 - sceaj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接