如何生成完全唯一的GUID?

5

是否有一种方法可以每次生成一个100%全新的GUID,而不会在整个应用程序中发生冲突?

由于我无法在八小时内回答我的问题,所以我想出了解决方案:

internal static class GuidGenerator
{
    private static readonly HashSet<Guid> _guids = new HashSet<Guid>();

    internal static Guid GetOne()
    {
        Guid result;

        lock (_guids)
            while (!_guids.Add(result = Guid.NewGuid())) ;

        return result;
    }
    internal static void Utilize(Guid guid)
    {
        lock (_guids)
            _guids.Remove(guid);
    }
}

这段代码能解决应用程序中的问题吗? 编辑:嗯,情况变得复杂了。线程安全会影响速度。

7
你需要使用无限长度的 GUID。 - Nasreddine
6
GUID不是应该全局唯一吗?对于整个应用程序而言,我认为.NewGuid()足够了。除非你在谈论跨宇宙唯一标识? - KMån
15
这段话的意思是,这个代码等同于在一个阳光普照、没有云彩的日子里待在室内以避免被闪电击中。 - Daniel Pratt
3
只要使用有限长度的GUID,就永远不能百分之百保证不会出现GUID冲突。Daniel和CodeInChaos的意思是,实际上发生冲突的概率非常小,你的应用程序更有可能因为其他原因(如运行它的硬件故障)而失败,而不是因为GUID冲突。 - Jared Ng
3
为什么不直接使用一个整数计数器并递增它? - CodesInChaos
显示剩余16条评论
11个回答

26

不,没有任何方法可以生成 绝对独一无二 的 GUIDs。GUID 只有 3.40282367 × 1038 种可能性,所以随着星系的碰撞,这些标识符也会发生冲突。即使是对于单个应用程序,也取决于应用程序有多少 GUIDs。除非您的应用程序比 Google 所有索引器的总和还要大,否则您不需要为此失眠。只需使用 Guid.NewGuid()


1
我认为我们可以在Skeet回答这个话题之后询问他有关Google索引程序的问题。 - AgentFire

17

当然可以。GUID只是一个128位的值。因此,使用128位整数(例如由两个ulong值表示)并递增它。当您达到128位整数类型的最大值时,您已生成所有可能的GUID。例如:

public IEnumerable<Guid> GetAllGuids()
{
    unchecked
    {
        byte[] buffer = new byte[16];
        ulong x = 0UL;
        do
        {
           byte[] high = BitConverter.GetBytes(x);
           Array.Copy(high, 0, buffer, 0, 8);
           ulong y = 0UL;
           do
           {
               y++;
               byte[] low = BitConverter.GetBytes(y);
               Array.Copy(low, 0, buffer, 8, 8);
               yield return new Guid(buffer);
           } while (y != 0UL);
           x++;
        } while (x != 0UL);
    }
}

注意:

  • 这绝对不是最有效的方法。
  • 遍历所有可能的ulong值很麻烦 - 我不喜欢使用do...while...
  • 如评论中所述,这将生成无效的UUID值。

当然,这样做并不是随机的……

实际上,正如其他人所提到的,使用Guid.NewGuid产生冲突的概率非常小。


2
@Sangram:我不确定你想表达什么。使用正常方法,发生冲突的可能性很小但不为零。我的代码生成每个可能的GUID,没有重复。 - Jon Skeet
1
@AgentFire:在我看来,那比使用do/while版本要糟糕得多。 - Jon Skeet
1
@AgentFire:是的,我更喜欢这种方式。我个人不喜欢在较大的表达式中使用前缀/后缀递增 - 对我来说,do/while比那更清晰。 - Jon Skeet
1
@AgentFire:实际上我没有。尝试编写完整的内容,你可能会明白为什么我没有使用它。提示:如果您使用 y < ulong.MaxValue,则永远不会看到 y == ulong.MaxValue...(循环开始时从未有过 y 的值意味着您应该终止。) - Jon Skeet
1
@Medinoc:这些在任何地方都经过验证了吗?根据不同的定义,结果可能不是有效的Guid,但我认为.NET本身不会抱怨。我已经添加了一条注释。 - Jon Skeet
显示剩余8条评论

7
不是100%。但是如果您的GUID生成器工作正常,碰撞概率非常小。这实际上可以视为0。
一个随机生成的(类型4)GUID大约有120个随机比特。从生日悖论中可以看出,一旦您生成约2^60或10^18个GUID,碰撞就会变得很可能,这是一个非常大的数字。
因此,简单地使用Guid.NewGuid()就足够了。
您提出的解决方案在我看来不是一个好主意:
它需要大量的内存,如果你有很多GUID
由于您需要在本地知道所有GUID,因此首先没有理由使用GUID。一个简单的整数计数器同样可以完成工作。
随机GUID碰撞的可能性比故障硬件破坏数据结构要小。
您的代码本身对我来说看起来是正确的。也就是说,如果您注册了所有GUID,您的硬件工作完美,并且软件没有其他错误,则保证不会发生碰撞。
当然,它也不是线程安全的,这对于静态方法来说是意外的。

4

如果您使用有限数量的字符,则根据鸽笼原理(也称为狄利克雷原理),总会有可能发生碰撞。


3

3
Bobby Tables 这样滥用你的数据库 :P - Petar Minchev

3
如果您需要在上下文中使用唯一的GUID,只需从00000000-0000-0000-0000-000000000000开始并进行递增。除非您达到FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF,否则所有生成的GUID都将是唯一的。

2

存储当前GUID仅适用于少量GUID,但这些GUID的碰撞几率已经非常低了。

在实际情况下,您可能会定期生成数百万甚至数十亿个GUID,在存储128位值以确保唯一GUID方面的开销变得不切实际(每个GUID 16字节)。

对于仅10,000,000,000个GUID,您需要160,000,000,000个字节= 156,250,000 Kbytes = 152,588 Mbytes = 149 Gbytes。

在大型表中查找时间也会使生成新唯一GUID变得非常缓慢(在CPU时间范围内),特别是当新GUID与现有值发生冲突时,需要生成新GUID - 然后需要进行检查等操作。

生成“随机” 128位值,或者使用类似于(当前时间*处理器时钟)的东西,可能会足够接近 - 只需要45位即可存储1,000年的毫秒计数。 128位为您提供9,671,406,556,917,033,397,649,408倍的价值。

无论您做什么,128位值都将发生冲突,即使使用计数器。


2

这取决于你想要什么。如果你想在生成GUID时保证其唯一性,这是可以做到的。只需维护一个GUID列表,每当需要创建新的GUID时,循环查找未在列表中的GUID。

如果你想要全球范围内的唯一性,也就是说对于整个地球上正在使用的所有GUID来说都是唯一的,那么这是不可能实现的。


2
你可以使用 Guid.NewGuid()。它会为你生成GUID,我相信你不会遇到与另一个GUID冲突的情况。

1

Guid.NewGuid() 是生成 GUID 最不可能与其他 GUID 发生冲突的方法。除非您生成一个 GUID 并查看现有的 GUID,否则无法百分之百确定。


你的意思是 Guid.NewGuid 很可能会导致冲突,还是很可能不会导致冲突?我有点困惑。 - Ayush
1
很可能不会。这是最不容易导致冲突的方法。 - KallDrexx

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