当前位置: 首页 > news >正文

Go 语言 UUID 库 google/uuid 源码解析:UUID version1 的实现

google/uuid 库地址

关于 UUID 的总体介绍可以查看这篇文章,其包含阅读此篇文章的前置内容。

UUID version 1 在 RFC 4122 文件中定义,其实现基于节点 ID、时钟序列以及当前时间(距离格里历改日【1582年10月15日】 的100纳秒数,具体介绍可以看Go 语言 UUID 库 google/uuid 源码解析:时钟信息)。

目前还没有详细的文章介绍节点 ID 的实现,但是可以知道的是,节点 ID 是利用网络接口硬件地址生成的,定义在 node.go 文件的 setNodeInterface 函数中。其逻辑大致如下:

  1. 如果你指定了网络接口的名称,则它回尝试获取该接口的硬件地址(即 MAC 地址)作为节点 ID。
  2. 如果没有指定,则选择第一个可用接口的硬件地址。
  3. 如果没有可用的硬件地址则会随机生成一个节点 ID。

UUID version 1 在 google/uuid 中的实现则定义在 version1.go 文件中。

函数接口

UUID version 1 定义的接口为 NewUUID(),其返回值为 (UUID, error) 即返回 UUID 序列以及错误信息。其具体代码放在文章末尾,存在困惑的地方,可以看看源码。

具体实现

UUID 的存储结构

首先我们知道 UUID 实际是长 16 字节的序列,其表现是 32 个十六进制数。google 则是将 UUID 序列使用长 16 的字节切片进行存储。其实现如下:

  1. 首先在 uuid.go 文件中声明 type UUID [16]byte 将长 16 的字节切片起别名为 UUID,使其含义更加清晰。
  2. 然后在 version1.go 文件 NewUUID 函数中定义 uuid 变量供后续使用 var uuid UUID

获取时间与时钟序列

时间戳与时钟序列通过 GetTime() 函数直接获取。(GetTime() 的详细介绍可以看 Go 语言 UUID 库 google/uuid 源码解析:时钟信息)。得到两个变量 nowseqnow, seq, err := GetTime()

分割时间信息

首先我们需要知道获取到的 now 类型为 int64 ,即其二进制有 64 位,uuid 中的时间信息会被“切割”为三段:timeHi(16)、timeMid(16)、timeLow(32),具体“切割”如下:

xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
timeHi		    /timeMid		 /timeLow

需要提起知道的是类似 x & 0xff 的代码用于保留低位比特值,抹除高位比特值。示例如下:

x:      10101010 11011011
0xff:   00000000 11111111
-------------------------
&:      00000000 11011011

“切割”实现代码如下:

// 低32位
timeLow := uint32(now & 0xffffffff)
// 高32位中的低16位
timeMid := uint16((now >> 32) & 0xffff)
// 高16位
timeHi := uint16((now >> 48) & 0x0fff)

上述代码详解如下:

  • timeLow
    1. now & 0xffffffff:取 now(int64) 的低 32 位。
    2. uint32(x):将结果转为 uint32。
  • timeMid
    1. now >> 32 将 now(int64) 的高 32 位挪到低 32 位,高 32 位置 0。
    2. (x) & 0xffff 取当前(新)低 32 位中的低 16 位。
    3. uint16(x) 将结果转为 uint 16。
  • timeHi
    1. now >> 48 将 now(int64) 的高 16 位挪到低 16 位,高 48 位置 0。
    2. (x) & 0xfff 取当前(新)低 16 位中的低 12 位。
    3. uint16(x) 将结果转位 uint 16。

之所以 timeHi 只取到低 12 位,是因为需要保留 4 位作为标志位,此次是用于标识 UUID 版本。

我们需要提前知道的是:类似于 x |= 0x1000 的代码,使用于将某个特殊位置为 1 的,此次是将第 13 位(从右往左)置为1:

x:       00000011 00110011
0x1000:  00010000 00000000
--------------------------
|:       00010000 00110011

标识版本代码如下:

timeHi |= 0x1000 // 版本 1

将时间信息和时钟序列放置到 uuid 的正确位置

首先我们需要知道最终的 uuid 结构组成如何:

(🂓代表标志位)

十六进制字符数|8							     |4				  |4  🂓  		   |4			    |12
二进制数     |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
具体含义     |timeLow						 |timeMid		  |timeHi		   |seq			    |clockSeq								       

我们只需要按照这个结构以大端序的方式(RFC 变体使用大端序)依此填入即可,实现代码如下:

// 将 timeLow 填充到前 4 个字节
binary.BigEndian.PutUint32(uuid[0:], timeLow)
// 将 timeMid 填充到第4和第5个字节
binary.BigEndian.PutUint16(uuid[4:], timeMid)
// 将 timeHi 填充到第6和第7个字节
binary.BigEndian.PutUint16(uuid[6:], timeHi)
// 将时钟序列填充到第8和第9个字节
binary.BigEndian.PutUint16(uuid[8:], seq)

填充 NodeID

时间和时钟序列填充完毕之后,最后只需填充 NodeID 即可。其基本逻辑为:

  1. 加锁
  2. 如果当前 nodeID 未设置,则通过 setNodeInterface 生成。
  3. 将 nodeID 拷贝至 uuid 的第10到最后一个节点。
  4. 解锁

实现代码如下:

	nodeMu.Lock()if nodeID == zeroID {setNodeInterface("")}copy(uuid[10:], nodeID[:])nodeMu.Unlock()

返回 uuid

最后返回填充好的 uuid 和 nil(error) 即可。return uuid, nil

到这里,完整的 UUID version1 源码解析便完成了,希望你能有所收获。

NewUUID 源码

func NewUUID() (UUID, error) {var uuid UUIDnow, seq, err := GetTime()if err != nil {return uuid, err}// 标志位//   🂓// xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx// timeHi		   /timeMid			/timeLow// 低32位timeLow := uint32(now & 0xffffffff)// 高32位中的低16位timeMid := uint16((now >> 32) & 0xffff)// 高16位timeHi := uint16((now >> 48) & 0x0fff)// 将第4位置为1,作为标志位,标志为版本号1timeHi |= 0x1000 // 版本 1// 8							   /4				/4  🂓  		  /4			   /12 											    /16进制字符数// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/二进制数// timeLow						   /timeMid			/timeHi			 /seq			  /clockSeq									       /具体含义binary.BigEndian.PutUint32(uuid[0:], timeLow)binary.BigEndian.PutUint16(uuid[4:], timeMid)binary.BigEndian.PutUint16(uuid[6:], timeHi)binary.BigEndian.PutUint16(uuid[8:], seq)nodeMu.Lock()if nodeID == zeroID {setNodeInterface("")}copy(uuid[10:], nodeID[:])nodeMu.Unlock()return uuid, nil
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Redis Cluster 模式 的具体实施细节是什么样的?
  • 【IT领域新生必看】 Java编程中的重载(Overloading):初学者轻松掌握的全方位指南
  • 基于Java的网上花店系统
  • 认识异常详解
  • 01背包问题-队列分支限界法-C++
  • 数据结构之“栈”(全方位认识)
  • C++初学者指南-4.诊断---基础:警告和测试
  • 宿舍报修小程序的设计
  • 从入门到深入,Docker新手学习教程
  • 网络-calico问题分析
  • Java面试八股之MySQL存储货币数据,用什么类型合适
  • 24.6.30
  • C++笔试强训2
  • c_各个unsigned int 和 int的取值范围
  • Exploting an API endpoiint using documentation
  • python3.6+scrapy+mysql 爬虫实战
  • $translatePartialLoader加载失败及解决方式
  • __proto__ 和 prototype的关系
  • 【347天】每日项目总结系列085(2018.01.18)
  • 2017前端实习生面试总结
  • CSS 三角实现
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JavaScript异步流程控制的前世今生
  • supervisor 永不挂掉的进程 安装以及使用
  • VuePress 静态网站生成
  • 动态规划入门(以爬楼梯为例)
  • 近期前端发展计划
  • 离散点最小(凸)包围边界查找
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 线上 python http server profile 实践
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • # C++之functional库用法整理
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (02)Hive SQL编译成MapReduce任务的过程
  • (10)STL算法之搜索(二) 二分查找
  • (27)4.8 习题课
  • (arch)linux 转换文件编码格式
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (四)Controller接口控制器详解(三)
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (原)Matlab的svmtrain和svmclassify
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .Net的DataSet直接与SQL2005交互
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • @Autowired注解的实现原理
  • [23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians
  • [ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)
  • [CC-FNCS]Chef and Churu
  • [CQOI 2011]动态逆序对
  • [EFI]ASUS EX-B365M-V5 Gold G5400 CPU电脑 Hackintosh 黑苹果引导文件