如何从GUID生成8个字节的唯一ID?

时间:2011-04-15 14:23:37

标签: c# .net

我尝试在我们的C#应用​​程序中使用long作为唯一ID(对于我们的事件,不是全局的,仅用于一个会话)。您知道以下内容是否会生成唯一的长ID?

public long GenerateId()
{
 byte[] buffer = Guid.NewGuid().ToByteArray();
 return BitConverter.ToInt64(buffer, 0);
}

为什么我们不直接使用GUID?我们认为8字节长就足够了。

11 个答案:

答案 0 :(得分:12)

不,它不会。正如在Raymond Chen的博客上多次强调的那样,GUID被设计为整体上是独一无二的,如果你只删除它的一部分(例如,只从其128中删除64个字节),它将失去其(伪)唯一性保证


Here它是:

  

客户需要生成一个8字节的唯一值,他们最初的想法是生成一个GUID并丢弃后半部分,保留前八个字节。他们想知道这是不是一个好主意。

     

不,这不是个好主意。    (......)   一旦你看到这一切是如何工作的,很明显你不能只丢掉部分GUID,因为所有部分(除了固定部分)一起工作以建立唯一性。如果你取走这三个部分中的任何一个,算法就会崩溃。特别是,只保留前8个字节(64位)可以得到时间戳和4个常量位;换句话说,你所拥有的只是一个时间戳,而不是GUID。

     

因为它只是一个时间戳,所以你可能会发生碰撞。如果两台计算机同时生成这些“截断的GUID”之一,它们将生成相同的结果。或者,如果系统时钟由于时钟复位而及时反转,您将开始重新生成第一次生成的GUID。

<小时/>

  

我尝试在我们的C#应用​​程序中使用long作为唯一ID(不是全局的,仅用于一个会话。)用于我们的事件。你知道以下内容会生成一个唯一的长ID吗?

你为什么不用一个柜台?

答案 1 :(得分:2)

不,它不会。 GUID具有128位长度,长度仅为64位,您缺少64位信息,允许两个GUID生成相同的长表示。虽然机会很小,但它确实存在。

答案 2 :(得分:2)

根据Guid.NewGuid MSDN page

  

新Guid的值全部为0或等于任何其他Guid的可能性非常低。

因此,您的方法可能会生成一个唯一的ID,但不能保证。

答案 3 :(得分:2)

您仍然无法将16位值提取到8位值,同时仍保持相同的唯一性。如果唯一性至关重要,请不要“自己动手”。坚持使用GUID,除非你真的知道自己在做什么。

如果一个相对天真的唯一性实现就足够了,那么生成自己的ID更好,而不是从GUID派生它们。以下代码片段是从我经常使用的“本地唯一标识符”类中提取的。它可以很容易地定义字符输出的长度和范围。

using System.Security.Cryptography;
using System.Text;

public class LUID
{
    private static readonly RNGCryptoServiceProvider RandomGenerator = new RNGCryptoServiceProvider();
    private static readonly char[] ValidCharacters = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".ToCharArray();
    public const int DefaultLength = 6;
    private static int counter = 0;

    public static string Generate(int length = DefaultLength)
    {
        var randomData = new byte[length];
        RandomGenerator.GetNonZeroBytes(randomData);

        var result = new StringBuilder(DefaultLength);
        foreach (var value in randomData)
        {
            counter = (counter + value) % (ValidCharacters.Length - 1);
            result.Append(ValidCharacters[counter]);
        }
        return result.ToString();
    }
}

在这种情况下,为了明确的人类可读输出,它排除了1(一),I(i),0(零)和O(o)。

为了确定有效字符和ID长度的特定组合的“独特”有多么有效,数学很简单,但是对于各种类型的“代码证明”(Xunit)仍然很好:

    [Fact]
    public void Does_not_generate_collisions_within_reasonable_number_of_iterations()
    {
        var ids = new HashSet<string>();
        var minimumAcceptibleIterations = 10000;
        for (int i = 0; i < minimumAcceptibleIterations; i++)
        {
            var result = LUID.Generate();
            Assert.True(!ids.Contains(result), $"Collision on run {i} with ID '{result}'");
            ids.Add(result);
        }            
    }

答案 4 :(得分:1)

是的,这将是最可能唯一,但由于位数小于GUID,重复的可能性超过了GUID - 尽管仍然可忽略不计

无论如何,GUID本身确实不保证唯一性。

答案 5 :(得分:1)

var s = Guid.NewGuid().ToString();
var h1 = s.Substring(0, s.Length / 2).GetHashCode(); // first half of Guid
var h2 = s.Substring(s.Length / 2).GetHashCode(); // second half of Guid
var result = (uint) h1 | (ulong) h2 << 32; // unique 8-byte long
var bytes = BitConverter.GetBytes(result);

P上。很棒,伙计们,你在这里与主题入门者聊天。但是那些需要其他用户的答案呢,比如我

答案 6 :(得分:0)

像其他一些人所说的那样,仅仅采取部分指导是破坏其独特性的好方法。尝试这样的事情:

var bytes = new byte[8];
using (var rng = new RNGCryptoServiceProvider())
{
    rng.GetBytes(bytes);
}

Console.WriteLine(BitConverter.ToInt64(bytes, 0));

答案 7 :(得分:0)

根据当前时间戳(以秒为单位)激活8字节的Ascii85标识符。 保证每秒独一无二。在同一秒内,5个生成的ID没有碰撞的概率为85%。

private static readonly Random Random = new Random();
public static string GenerateIdentifier()
{
    var seconds = (int) DateTime.Now.Subtract(new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;
    var timeBytes = BitConverter.GetBytes(seconds);
    var randomBytes = new byte[2];
    Random.NextBytes(randomBytes);
    var bytes = new byte[timeBytes.Length + randomBytes.Length];
    System.Buffer.BlockCopy(timeBytes, 0, bytes, 0, timeBytes.Length);
    System.Buffer.BlockCopy(randomBytes, 0, bytes, timeBytes.Length, randomBytes.Length);
    return Ascii85.Encode(bytes);
}

答案 8 :(得分:0)

正如大多数其他答案中已经说过的那样:不,你可以只是参与GUID的一部分而不会失去唯一性。

如果您需要更短且更独特的内容,请阅读Jeff Atwood撰写的这篇博客文章:
Equipping our ASCII Armor

他展示了如何在不丢失信息的情况下缩短GUID的多种方法。最短的是20个字节(ASCII85 encoding)。

是的,这比你想要的8个字节长得多,但它是一个“真正的”唯一GUID ......虽然所有尝试将某些内容塞入8个字节的可能性都不会真正独特。

答案 9 :(得分:0)

在大多数情况下,两个半部分的按位异或就足够了

答案 10 :(得分:0)

这里的每个人都让这种方式变得比需要的更复杂。这是一个糟糕的主意。

GUID 1:AAAA-BBBB-CCCC-DDDD
GUID 2:AAAA-BBBB-EEEE-FFFF

扔掉每个 GUID 的后半部分,现在你有一个重复的标识符。 GUID 不能保证是唯一的,而且非常糟糕。您不应该依赖生成的内容的保证,并且不难解决这个问题。如果您需要对象、实体或其他任何东西的唯一标识符,让我们以数据库为例 - 这是最常见的,您应该生成一个 id,查看它是否已经存在,只有在不存在时才插入它。这在数据库中很快,因为大多数表都是基于 ID 索引的。 “最多。”如果您在内存或其他任何地方有某种小对象列表,您可能会将实体存储在某种哈希表中,您可以在其中查找它以查看生成的 GUID 是否已经存在。

总而言之,取决于您的实际用例是什么。数据库,首先找到 GUID,如果可能,重新生成,直到您可以插入新项目。这实际上只在不自动为表中的项目生成 ID 的关系数据库中很重要。 NoSQL DB 通常会生成唯一标识符。