能基本保证全球唯一性
使用了Mac地址,因此会暴露Mac地址和生成时间
基于时间的UUID为例先梳理UUID的结构:
UUID为32位的十六机制数,因此实际上是16-byte (128-bit),各位分别为:
时间值:在基于时间的UUID中,时间值是一个60位的整型值,对应UTC的100ns时间间隔计数,因此其支持支持一台机器每秒生成10M次。在UUID中,将这60位放置到了15~08这8-byte中(除了09位有4-bit的版本号内容)。
版本号:版本号即上文所说的五个版本,在五个版本的UUID中,都总是在该位置标识版本,占据 4-bit,分别以下列数字表示:
因此版本号这一位的取值只会是1,2,3,4,5
时钟序列:在基于时间的UUID中,时钟序列占据了07~06位的14-bit。不同于时间值,时钟序列实际上是表示一种逻辑序列,用于标识事件发生的顺序。在此,如果前一时钟序列已知,则可以通过自增来实现时钟序列值的改变;否则,通过(伪)随机数来设置。主要用于避免因时间值向未来设置或节点值改变可能导致的UUID重复问题。
节点值:在基于时间的UUID中,节点值占据了05~00的48-bit,由机器的MAC地址构成。如果机器有多个MAC地址,则随机选其中一个;如果机器没有MAC地址,则采用(伪)随机数。
能保证全球唯一性
很少使用,常用库基本没有实现
将基于时间的UUID(版本1)中时间戳前四位换为POSIX的UID或GID,其余保持一致。
不同名字空间或名字下的UUID是唯一的;相同名字空间及名字下得到的UUID保持重复。
MD5碰撞问题,只用于向后兼容,后续不再使用
将命名空间 (如DNS、URL、OID等) 及名字转换为字节序列;
通过MD5散列算法将上述字节序列转换为16字节哈希值 (MD5散列不再推荐,SHA1散列的20位只使用其15~00位);
将哈希值的 3~0 字节置于UUID的15~12位;
将哈希值的 5~4 字节置于UUID的11~10位;
将哈希值的 7~6 字节置于UUID的09~08位,并用相应版本号覆盖第9位的高4位 (同版本1位置);
将哈希值的 8 字节置于UUID的07位,并用相应变体值覆盖其高2位 (同版本1位置);
将哈希值的 9 字节置于UUID的06位 (原时钟序列位置);
将哈希值的 15~10 字节置于UUID的05~00位 (原节点值位置)。
实现简单
重复几率可计算
生成16byte随机值填充UUID。重复机率与随机数产生器的质量有关。若要避免重复率提高,必须要使用基于密码学上的假随机数产生器来生成值才行;
将变体值及版本号填到相应位置。
不同名字空间或名字下的UUID是唯一的;相同名字空间及名字下得到的UUID保持重复。
SHA1计算相对耗时
将命名空间 (如DNS、URL、OID等) 及名字转换为字节序列;
通过SHA1散列算法将上述字节序列转换为16字节哈希值 (MD5散列不再推荐,SHA1散列的20位只使用其15~00位);
将哈希值的 3~0 字节置于UUID的15~12位;
将哈希值的 5~4 字节置于UUID的11~10位;
将哈希值的 7~6 字节置于UUID的09~08位,并用相应版本号覆盖第9位的高4位 (同版本1位置);
将哈希值的 8 字节置于UUID的07位,并用相应变体值覆盖其高2位 (同版本1位置);
将哈希值的 9 字节置于UUID的06位 (原时钟序列位置);
将哈希值的 15~10 字节置于UUID的05~00位 (原节点值位置)。
go代码如下
package main
import (
"fmt"
"github.com/google/uuid"
)
func UUIDv1() uuid.UUID {
id, err := uuid.NewUUID()
if err != nil {
panic(err)
}
return id
}
func UUIDv2G() uuid.UUID {
id, err := uuid.NewDCEGroup()
if err != nil {
panic(err)
}
return id
}
func UUIDv2P() uuid.UUID {
id, err := uuid.NewDCEPerson()
if err != nil {
panic(err)
}
return id
}
func UUIDv3(data []byte) uuid.UUID {
id, err := uuid.NewDCEPerson()
if err != nil {
panic(err)
}
return uuid.NewMD5(id, data)
}
func UUIDv4() uuid.UUID {
return uuid.New()
}
func UUIDv5(data []byte) uuid.UUID {
id, err := uuid.NewDCEPerson()
if err != nil {
panic(err)
}
return uuid.NewSHA1(id, data)
}
func main() {
fmt.Println("---------------UUIDv1---------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv1())
}
fmt.Println("---------------UUIDv2G--------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv2G())
}
fmt.Println("---------------UUIDv2P--------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv2P())
}
fmt.Println("---------------UUIDv3---------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv3([]byte("encrypted")))
}
fmt.Println("---------------UUIDv4---------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv4())
}
fmt.Println("---------------UUIDv5---------------")
for i := 0; i < 5; i++ {
fmt.Println(UUIDv5([]byte("encrypted")))
}
}
GUID有两种解释:1.就是UUID 2.特指微软对UUID标准的实现(可能是v4,后文介绍)。可以具体语境去辨析是哪个意思。
GUID就是UUID的依据,可以参照以下:
typedef struct _GUID {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
byte Data4[8];
} GUID,
UUID,
*PGUID;
参考文章
作者采用如下C#代码进行测试
///
/// Simple script to check whether a C# Guid is really a UUID version 4.
///
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"{"Current",-10}{"Errors found",-10}");
Console.WriteLine($"{"-------",-10}{"------------",-10}");
var errorsFound = 0;
for (var i = 0; i < 10_000_000; i++)
{
var guid = Guid.NewGuid();
var uuidVersion = guid.ToString().Substring(14, 1);
var isVersion4 = uuidVersion == "4";
if (!isVersion4)
{
errorsFound += 1;
}
Console.Write($"\r{i, -10}{errorsFound, -10} ");
}
Console.WriteLine();
Console.ReadKey();
}
}
结果显示
Current Errors found
------- ------------
9999999 0
猜测总归不是看了源码,所以我们只能做出如下结论:
GUID极可能是UUIDv4
如果使用微软官方提供的API,有两种方式获取GUID
在C#中 微软专门提供了一个Guid结构 来帮助人们生成GUID,命名空间为 System中 使用C#生成GUID的方法如下:
使用如下方法生成:
Guid.NewGuid()
如果要转换成为字符串使用,则可以使用如下方法:
Guid.NewGuid().ToString(string format)
其中 format 指示如何格式化GUID的格式 可以有不同的参数"N",“D”,“P”,“B”,null,当选择null时使用默认的参数"D"
使用C#语言生成 如下:
string guid1 = Guid.NewGuid().ToString("N");
string guid2 = Guid.NewGuid().ToString("D");
string guid3 = Guid.NewGuid().ToString("P");
string guid4 = Guid.NewGuid().ToString("B");
比较运算符可与 uniqueidentifier 值一起使用。不过,排序不是通过比较两个值的位模式来实现的。可针对 uniqueidentifier 值执行的运算只有比较运算(=、<>、<、>、<=、>=)以及检查是否为 NULL(IS NULL 和 IS NOT NULL)。不能使用其他算术运算符。除 IDENTITY 之外的所有列约束和属性均可对 uniqueidentifier 数据类型使用。
具有更新订阅的合并复制和事务复制使用 uniqueidentifier 列来确保在表的多个副本中唯一地标识行。
sql server中 生成 GUID的时候 使用如下方法生成:
-- Creating a local variable with DECLARE/SET syntax.
DECLARE @myid uniqueidentifier
SET @myid = NEWID()
PRINT 'Value of @myid is: '+ CONVERT(varchar(255), @myid)
输出结果为:
Value of @myid is: 9A09488E-7D1D-4995-9A7D-3AFB736CA3FE
注意,NEWID 对每台计算机返回的值各不相同。所显示的数字仅起解释说明的作用。
要想使用SQL server和C#中生成一样格式,在C#中要使用默认的参数"D",才能使SQL server 和C#中的格式一致