44

我需要为我的数据库主键列生成唯一的 Long id。

我以为我可以使用UUID.randomUUID().getMostSignificantBits()但有时它会产生一些负长,这对我来说也是个问题。

是否可以只从 UUID 生成正长?会有数十亿个条目,所以我希望每个生成的密钥都必须是唯一的。

4

6 回答 6

53
UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE

这样做的原因是,当你用 1 按位 & 时,它允许相同的数字通过,当你用 0 按位 & 时,它会阻止它,结果为 0。现在,二进制中的 Long.MAX_Value 是

0111111111111111111111111111111111111111111111111111111111111111 

这是 0 后跟 63 个 1(总共是 64 位,在 java 中很长)

因此,当您使用上述数字按位 & 一个数字 X 时,您将得到相同的数字 X,除了最左边的位现在变成零。这意味着您只更改了该数字的符号而不是值。

于 2017-01-12T12:33:47.170 回答
12

正如其他人所写的那样, long 没有足够的空间容纳唯一的数字。但在许多情况下,对于特定用途,一个数字可能是独一无二的。例如,具有纳秒精度的时间戳通常就足够了。为了得到它,将当前毫秒向左移动 20 位以分配纳秒空间,然后用纳秒覆盖它:

(System.currentTimeMillis() << 20) | (System.nanoTime() & ~9223372036854251520L);

nano & ~9223372036854251520L部分采用当前的纳秒并将前 44 个字节设置为 0,只留下代表纳秒到一毫秒的右侧 20 位(999999 纳秒)它与以下内容相同:

nanoseconds & ~1111111111111111111111111111111111111111111100000000000000000000

旁注:不应使用纳秒来表示当前时间,因为它们的起点在时间上不固定,并且在达到最大值时会被回收。

您可以使用任何其他位操作。通常最好考虑当前时间和其他一些东西,例如当前线程 id、进程 id、ip。

于 2016-05-19T15:57:19.380 回答
8

看看http://commons.apache.org/sandbox/commons-id//index.html 它有一个 LongGenerator 可以给你你所需要的。

此外,如果您使用的是 Hibernate,那么您可以要求它为您生成 ID(它有多种算法可供您选择),如果没有,您可以查看它们的实现,例如http://grepcode.com/文件/repo1.maven.org/maven2/hibernate/hibernate/2.1.8/net/sf/hibernate/id/TableHiLoGenerator.java#TableHiLoGenerator

于 2013-03-03T12:00:37.573 回答
3

我刚刚遇到了这个解决方案。我暂时试图理解解决方案。它说的是 twitter 雪花的 Java 实现。基于 twitter 雪花 ID 生成算法的 64 位顺序 ID 生成器。

https://github.com/Predictor/javasnowflake

欢迎任何建议。

于 2013-03-03T11:37:12.740 回答
2

此代码的灵感来自@Daniel Nuriyev 的回答。但是,当在同一毫秒内发生碰撞时,使用计数器(或我所见过的鉴别器)而不是使用纳米时间:

private static long previousTimeMillis = System.currentTimeMillis();
private static long counter = 0L;

public static synchronized long nextID() {
    long currentTimeMillis = System.currentTimeMillis();
    counter = (currentTimeMillis == previousTimeMillis) ? (counter + 1L) & 1048575L : 0L;
    previousTimeMillis = currentTimeMillis;
    long timeComponent = (currentTimeMillis & 8796093022207L) << 20;
    return timeComponent | counter;
}

此方法通过将毫秒时间戳组件与计数器组件打包在一起来生成半唯一 ID。该算法允许在冲突开始发生之前的同一毫秒内生成大约一百万个(或准确地说是 1048575 个)唯一 ID。唯一 ID 一直生成到 2248 年,此时它将环绕并再次从 0 开始。

ID生成如下:

自纪元以来的毫秒数:

|0|000000000000000000000010110111101111100110001001111100101011111|

与 (8796093022207L) 按位与:

|0|000000000000000000001111111111111111111111111111111111111111111|

为您提供 43 个最低有效位作为时间分量。

然后将其向左移动 20 位,得到:

|0|0010110111101111100110001001111100101011111|00000000000000000000|

与 20 位计数器的按位或(例如,如果计数器为 3)为您提供:

|0|0010110111101111100110001001111100101011111|00000000000000000101|

时间分量仅使用 43 位(而不是 44 位),因为我们不希望更改最高有效位(这是数字的符号)。这导致仅生成正 ID。

于 2020-01-07T10:59:37.127 回答
1

我想在应用程序端执行此操作,因为如果我将在数据库端执行此操作,我必须再次触发一个查询以获取行的 id..我想避免这种情况。

不!您可以使用 AUTOINCREMENT 主键,并在 JDBC 中使用 INSERT 检索生成的键。

String insertSQL = "INSERT INTO table... (name, ...)"
        + " VALUES(?, ..., ?)";
try (Connection connection = getConnection();
        PreparedStatement stmt = connection.prepareStatement(insertSQL,
                Statement.RETURN_GENERATED_KEYS)) {
    stmt.setString(1, ...);
    stmt.setInt(2, ...);
    stmt.setBigDecimal(3, ...);
    ...
    stmt.executeUpdate();

    ResultSet keysRS = stmt.getGeneratedKeys();
    if (keysRS.next()) {
        long id = keysRS.getInt(1);
    }
}

这更有效,肯定更容易,更安全。UUID 是 128 位。仅使用 64 位会降低其唯一性。所以至少主观上不是100%完美。至少 XOR ( ^) 两个长部分。

于 2020-01-07T11:16:45.340 回答