深度讲解 UUID/GUID 的结构、原理以及生成机制

目录

一. 前言

二. 被广泛使用

三. UUID 的结构

3.1. 必须了解的

3.2. 十六进制数字字符(hexDigit)

3.3. UUID 基本结构

3.4. 类型(变体)和保留位

3.5. 版本(子类型)

3.6. 时间戳

3.7. 时钟序列

3.8. 节点标志符

3.9. 小结

四. UUID 的生成

4.1. V4

4.2. V1

4.3. V3 和 V5

五. 获取 UUID V4

5.1. 正则 + Math.random

5.2. crypto.randomUUID

5.3. URL.createObjectURL

六. 后起之秀 NanoID


一. 前言

    UUID(Universally Unique IDentifier)通用唯一识别码 ,也称为 GUID(Globally Unique IDentifier)全球唯一标识符。

    UUID 是一个长度为128位的标志符,能够在时间和空间上确保其唯一性。UUID 最初应用于Apollo 网络计算系统,随后在 Open Software Foundation(OSF)的分布式计算环境(DCE)中得到应用。可以让分布式系统可以不借助中心节点,就可以生成唯一标识, 比如唯一的 ID 进行日志记录。

二. 被广泛使用

被微软 Windows 平台采用。Windows 举例2个使用场景:

1. COM 组件通过 GUID 来定义类标识符(CLSID)、接口标识符(IID)以及其他重要的标识,确保在整个系统中不会发生命名冲突。

2. Windows 注册表中很多项都使用 GUID 作为子键名,以便为特定程序或功能提供一个全球唯一的注册表路径。

    UUID 之所以被广泛采用,主要原因之一是它们的分配不需要中心管理机构介入。其具有唯一性和持久性,它们非常适合用作统一资源名称(URN)。UUID 能够无需注册过程就能生成新的标识符的独特优点,使得 UUID 成为创建成本最低的 URN 类型之一。

    那么 UUID 会重复嘛,由于 UUID 具有固定的大小并包含时间字段,在特定算法下,随着时间推移,理论上在大约公元3400年左右会出现值的循环,所以问题不大。

    由于 UUID 是一个128位的长的标志符,为了便于阅读和显示,通常会将这个大整数转换成32(不包含连接符)个十六进制字符组成的字符串形式。如下:

crypto.randomUUID()
// 4d93f326-3f48-4a43-929d-b6489f4754b5`${crypto.randomUUID()}`.length 
// 长度:36`${crypto.randomUUID()}`.replace(/-/g, '').length
// 去掉连接符:32

这128位的组成,以及是怎么变成32位的十六进制字符的,请继续往下看。

三. UUID 的结构

UUID 看似杂乱无章,其实内有乾坤。

3.1. 必须了解的

  • 比特(bit):二进制数字系统中的基本单位。一个比特可以代表二进制中的一个0或1。
  • 位(通常情况下与比特同义):二进制数系统中的一位,同样表示0或1。
  • 字节(Byte):字节是计算机中更常用的单位,用于衡量数据存储容量和传输速率。1字节等于8个比特。

总结起来就是:

  • 1 字节 = 8 位。
  • 1 位 = 1 比特。

    128位转为32个十六进制字符, 这个十六进制字符是什么呢,其专业名字为hexDigit,是 UUID中我们肉眼可见的最小单元。

3.2. 十六进制数字字符(hexDigit)

    hexDigit , 十六进制数字字符,是一个长度为4比特,可以表示0(0b000)到15(0b1111)之间数值。其能转为16进制的相对应符号,其取值范围为 0-9、a-f、A-F,即0123456789abcdefABCDEF 中的某一个值。

    所以, hexDigit 可以粗暴的理解为 0123456789abcdefABCDEF 中的某一个值。

 (0b1000).toString(16)     // 8(0b1111).toString(16)     // F

此外,还有一个 hexOctet两个连续 hexDigit 组成的序列, 占8个比特,即一个字节。

3.3. UUID 基本结构

UUID 结构图:

    这个图最上面的 0、1、2、3 不是表示位数,就是简单的表示10位数的值,9 之后就是 10、 11、12等等。

    如果不太好理解,换一张手工画的图(UUID 10类型的 V4 版本):

128 比特,16个字节即 16 hexOctet,就被如下瓜分了:

字段hexOctet(字节)位置备注
time_low40-3时间戳 的低位部分
time_mid24-5时间戳的中间部分
time_hi_and_version26-7时间戳高位部分与 版本 字段,其中12位代表时间戳的高12位,4位则用来标识UUID的版本号
clock_seq_hi_and_reserved18时钟序列 高位与 保留位
clock_seq_low19时钟序列低位
node610-15节点标识符,提供空间唯一性,通常基于MAC地址或随机数生成,以确保全局范围内的唯一性

    要想完整理解这个 6 部分组成,必然要理解备注中被加粗的几个概念。保留位,版本, 时间戳, 时钟序列 ,节点标志符。

3.4. 类型(变体)和保留位

    UUID 可以分为四种类型(变体),怎么识别是哪种类型(变体)呢,UUID 有对应的 Variant 字段去标记,可以参见协议的 4.1.1. Variant 部分。

    Variant 字段位于 UUID 的第8个字节即 clock_seq_hi_and_reserved 部分的第6-7位。

    以外所有其他位的含义都是依据 Variant 字段中的比特位设置来解读的。从这个意义上讲,Variant 字段更准确地说可以被称作类型字段;然而为了与历史文档兼容,仍沿用“Variant”这一术语。

下表列出了 Variant 字段可能的内容,其中字母“x”表示无关紧要或不关心的值:

  • Msb0(最高有效位0):此为最高位。
  • Msb1:次高位。
  • Msb2:第三高位。
Msb0Msb1Msb2描述
0xx保留,用于NCS(Network Computing System)向后兼容
10x此文档中指定的variant变体
110保留,用于微软公司系统的向后兼容
111保留供未来定义

    类型(变体)的标志符可以是 2 位也可是 3 位,本文围绕的的是 RFC4122: A Universally Unique IDentifier (UUID) URN Namespace 类型(变体), 即上面表格的第二行,其第三高位 为 x,表示该值并无意义,所以该版本只需要 10 即可。

    10 开头的 hexDigit 十六进制数字字符,其只有四个值:

0b1000   => 8
0b1001   => 9
0b1010   => a
0b1011   => b

    用简单的图示表示,就是 下面 y 的部分只会是这四个值 8、9、a、b 其中的某个值。xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx。简单测一测:

所以,一个 RFC4122 版本的 UUID 正宗不正宗,这么验证也是一种手段。

3.5. 版本(子类型)

    上面提到了 UUID 的类型(变体), 而这里版本,可以理解为某个类型(变体)下的不同子类型。 当然本文讨论的是 变体10 即 RFC4122 下的版本(子类型)。 UUID 的类型(变体)有字段标记,当然这里的版本也有。即版本号 time_hi_and_version 的第12至15位:

V4 版本如下:

一共有5个版本:

Msb0Msb1Msb2Msb3版本号描述
00011时间基版本。由时间戳、时钟序列、节点标识符以及版本号和变体字段组成。
00102类似于版本1,但使用DCE安全标识代替MAC地址。
00113基于命名空间名称和名字的MD5散列结果,加上版本号和变体字段。
01004完全基于随机或伪随机数据生成,不依赖于时间戳和硬件地址。
01015与版本3类似,但使用SHA-1算法替换MD5进行散列计算。

    用简单的图示表示,就是下面 V 的部分只会是这五个值 1、2、3、4、5 其中的某个值。xxxxxxxx-xxxx-Vxxx-yxxx-xxxxxxxxxxxx。借用 uuid 库演示一下:

3.6. 时间戳

先回顾一下两张图:

    第一张是 UUID 各部分的组成,time_low ,time_mid, time_hi_and_version 包含了时间戳的不同部分。

    第二张是 UUID 的五个版本,但是只有 V1 和 V2 提到了时间戳,也确实是这样,除了 V1 和 V2版本真正用了时间戳,其余版本通过不同手段生成了数据填充了 time_low ,time_mid, time_hi_and_version 这三个部分。

    那这个时间戳是开发者们常用的 Date.now() 这个时间戳吗, 答案当然不是。

    这里的时间戳是一个60位长度的数值。对于 UUID 版本1和2,它通过协调世界时(UTC)表示,即从1582年10月15日0点0分0秒开始算起的100纳秒间隔计数。

    比如 2024年1月1日0时0分0秒,这个值时间戳怎么算呢:

const startOfUuidEpoch = new Date('1582-10-15T00:00:00.000Z');
const uuidTimestampFromDate = (date) => {// 直接计算给定日期距离UUID纪元开始的毫秒数const msSinceUuidEpoch = date.getTime() - startOfUuidEpoch.getTime();// 将毫秒转换为100纳秒的整数倍,  1 毫秒=1000000 纳秒const uuidTimestampIn100Ns = Math.floor(msSinceUuidEpoch * 10000); // 每毫秒乘以10,000得到100纳秒return uuidTimestampIn100Ns;
};// 计算2024年1月1日对应的UUID V1版本时间戳
const targetDate = new Date('2024-01-01T00:00:00.000Z');
const uuidV1Timestamp = uuidTimestampFromDate(targetDate); 
// 139233600000000000

要保存为60位, 并划分高位(12),中间(16),低位三部分(32)

uuidV1Timestamp.toString(2).padStart(60,'0')
// 000111101110101010000011100010110100110011001000000000000000time-high     time-mid          time-low
000111101110 1010100000111000 10110100110011001000000000000000

    在不具备 UTC 功能但拥有本地时间的系统中,只要在整个系统内保持一致,也可以使用本地时间替代 UTC。然而,这种方法并不推荐,因为仅需要一个时区偏移量即可从本地时间生成 UTC 时间。

    对于 UUID 版本3或5,时间戳是一个根据 4.3 Algorithm for Creating a Name-Based UUID,由名称构建的60位值,V3 和 V5 区别是在算法上。

    而对于 UUID 版本4,时间戳则是一个随机或伪随机生成的60位值,具体细节参见第4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers

小结:

  • 时间戳是即从1582年10月15日0点0分0秒开始算起的100纳秒间隔计数,是一个60位值,被分为 高位,中间,低位三部分填充到UUID中。
  • 只有 V1 和 V2  真正意义上用了时间戳
  • V3 和 V5 由名字构建而成的60位值
  • V4 随机或伪随机生成的60位值。

3.7. 时钟序列

    时钟序列(clock sequence)用于帮助避免因系统时间被设置回溯或节点 ID 发生变化时可能出现的重复标识符。

    举个实例,手动把系统的时间设置为一个过去的时间,那么就可能导致生成重复的 UUID。

    协议考虑到了这点,就增加了时钟序列,增加一个变数,让结果不一样,当然如果序列也是不变的,那么还是可能重复,所以这个时钟序列也是会变化的。

    如果系统时钟被设置为向前的时间点之前,或者可能已经回溯(例如,在系统关机期间),并且UUID 生成器无法确定在此期间没有生成时间戳更大的 UUID,则需要更改时钟序列。若已知先前时钟序列的值,可以直接递增;否则应将其设置为一个随机或高质量的伪随机值。

    同样,当节点 ID 发生变化(比如因为网络适配器在不同机器间移动),将时钟序列设置为随机数可以最大限度地降低由于各机器之间微小时间设置差异导致重复 UUID 的可能性。尽管理论上知道与变更后的节点 ID 关联的时钟序列值后可以直接递增,但这种情况在实际操作中往往难以实现。

    时钟序列必须在其生命周期内首次初始化为随机数,以减少跨系统间的关联性。这提供了最大程度的保护,防止可能会快速在系统间迁移或切换的节点标识符产生问题。初始值不应与节点标识符相关联。

    同样的,这个时间序列只在 V1 和 V2 是真的按照上面的规则或者约定来执行的。

    对于 UUID 版本3或5,时钟序列是一个由第 4.3 Algorithm for Creating a Name-Based UUID 节描述的名称构建的14位值。

    而对于 UUID 版本4,时钟序列则是一个如第4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers 节所述随机或伪随机生成的14位值。

3.8. 节点标志符

    空间唯一节点标识符,用来确保即便在同一时间生成的 UUID 也能在特定网络或物理位置上保持唯一性。

    对于 UUID V1,这个节点标识符通常基于网络适配器的 MAC 地址或者在没有硬件 MAC 地址可用时由系统自动生成一个伪随机数。它的目的是反映生成 UUID 的设备在网络或物理空间中的唯一性,即使在相同的时序和时钟序列条件下,不同的设备也会因为其独特的节点标识符而产生不同的UUID。

    在 UUID V2 中,虽然不常用,但节点标识符的概念同样适用,用于标识系统的唯一性,只不过这里的“空间”更多地指向组织结构或其他逻辑意义上的空间划分。

    总之,空间唯一节点标识符是为了保证在分布式系统环境下,即使时间戳相同的情况下也能生成唯一的 UUID,以区分不同物理节点上的事件或资源。

    对于 UUID 版本3或5: 节点字段(48位)是根据第4.3节描述的方法,从一个名称构造而来。

    对于 UUID 版本4: 节点字段(同样是48位)是一个随机或伪随机生成的值。

3.9. 小结

从 V1 和 V2 版本来看, UUID 最后是想通过时间和空间上两层手段保证其唯一性:

  • 时间: 时间戳 + 时钟时序。
  • 空间: 节点标志符(比如 MAC 地址)。

同时考虑了类型(变体)和版本(子类型),即下面这些组信息组成了 UUID:

  • 时间戳
  • 时钟序列
  • 节点标志符
  • 保留位:即类型(变体)信息
  • 版本:V1 到 V5。

    因为保留位和版本信息本身是固定的,是可以从最后的32位16进制字符是可以直接或者间接看到的。再回顾这张图,是不是比较清晰了:

四. UUID 的生成

协议中有具体描述 V1、V3 和 V5 以及 V4 的基本流程或者约束。

4.1. V4

    浏览器和 nodejs 内置的了 V4 的生成函数, 而且其生成规则相对简单。对应着协议 4.4. Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers。

    版本4的 UUID 旨在通过真正的随机数或伪随机数生成 UUID。其生成算法相对简单,主要依赖于随机性。

生成算法步骤如下:

  1. 在 UUID 结构中的 clock_seq_hi_and_reserved 部分,将最高两位有效位(即第6位和第7位)分别设置为0和1。
  2. 在 UUID 结构中的 time_hi_and_version 字段,将最高四位有效位(即第12位至第15位)设置为来自第 4.1.3节 的4位版本号,对于版本4 UUID,这个版本号是固定的0100
  3. 将除了以上已设定的位之外的所有其他位设置为随机(或伪随机)选取的值。

不好理解,就看这张图:

    关于随机性安全要求, 引用了 BCP 106 标准文档,即 RFC 4086。RFC 4086 是一份由 IETF 制定的最佳当前实践(Best Current Practice, BCP)文档,其标题为“Security Requirements for Randomness”,该文档详细阐述了在实现安全协议与系统时所需的随机数生成器的要求和特性,确保生成的随机数具有足够的不可预测性和熵,能满足各类安全应用,包括但不限于密码学应用中的随机性需求。

    总之,生成版本4 UUID的过程中,首先对特定字段的几位进行固定设置以标明版本和时钟序列特征,然后其余所有位均通过随机或伪随机过程填充数值,以此确保生成的 UUID 具备全球唯一性和较强的随机性。

4.2. V1

对应这协议 4.2.2. Generation Details,按照以下步骤生成的:

  1. 确定时间戳和时钟序列:遵循第 4.2.1 节描述的方法,获取基于 UTC 的时间戳以及用于 UUID 的时钟序列。
  2. 处理时间戳和时钟序列:将时间戳视为一个 60 位无符号整数,时钟序列视为一个 14 位无符号整数,并按顺序编号每个字段中的位,最低有效位从0开始计数。
  3. 设置时间低位字段(time_low field):将其设置为时间戳的最低有效 32 位(位 0 到 31),保持相同的位权重顺序。
  4. 设置时间中间字段(time_mid field):将其设置为时间戳中的位 32 到 47,同样保持位权重顺序一致。
  5. 设置时间高位及版本字段(time_hi_and_version field)的低 12 位(位 0 到 11):将其设置为时间戳的位 48 到 59,保持位权重顺序一致。
  6. 设置时间高位及版本字段的高 4 位:将这 4 位(位 12 到 15)设置为对应于所创建 UUID 版本的 4 位版本号。
  7. 设置时钟序列低位字段(clock_seq_low field):将其设置为时钟序列的最低有效 8 位(位 0 到 7),同样保持位权重顺序一致。
  8. 设置时钟序列高位及保留字段的低 6 位(clock_seq_hi_and_reserved field 的位 0 到 5):将其设置为时钟序列的最高有效 6 位(位 8 到 13),保持相同位权重顺序。
  9. 设置时钟序列高位及保留字段的高 2 位:将这 2 位(位 6 和 7)分别设置为 0 和 1,以满足版本 1 UUID 的标准格式要求。
  10. 设置节点字段(node field):将其设置为 48 位的 IEEE MAC 地址,地址中的每一位都保持原有的位权重顺序。

4.3. V3 和 V5

    对应协议的 4.3. Algorithm for Creating a Name-Based UUID。

    版本3或5的 UUID 设计用于从特定命名空间(name space)内的且在该命名空间内唯一的名字(names)生成UUID。这里的名字(names)和命名空间(name space)的概念应该广泛理解,不仅限于文本名称。例如,一些命名空间包括域名系统(DNS)、统一资源定位符(URLs)、ISO 对象标识符(OIDs)、X.500区别名(DNs)以及编程语言中的保留字等。在这些命名空间内分配名称和确保其唯一性的具体机制或规则不在本规范的讨论范围内。

对于这类 UUID 的要求如下:

  1. 在同一命名空间内,使用相同名称在不同时间生成的 UUID 必须完全相同。
  2. 在同一命名空间内,使用两个不同名称生成的 UUID 应当是不同的(概率极高)。
  3. 在两个不同命名空间内,使用相同名称生成的 UUID 也应当是不同的(概率极高)。
  4. 如果两个由名称生成的 UUID 相同,则它们几乎肯定是由同一命名空间内的相同名称生成的。

生成基于名称和命名空间的 UUID 的具体算法步骤如下:

  1. 为给定命名空间内所有由名称生成的UUID分配一个作为“命名空间ID”的UUID;参见附录C中预定义的一些值。
  2. 选择MD5 [4] 或SHA-1 [8] 其中的一种哈希算法;如果不考虑向后兼容性,建议优先使用SHA-1。
  3. 将名称转换为其命名空间规定的标准化字节序列形式,并将命名空间ID以网络字节序排列。
  4. 计算命名空间ID与名称连接后的哈希值。
  5. 将哈希值的前四个八位组(octets 0-3)赋给时间低位字段(time_low field)的前四个八位组。
  6. 将哈希值的第五和第六个八位组赋给时间中间字段(time_mid field)的前两个八位组。
  7. 将哈希值的第七和第八个八位组赋给时间高位及版本字段(time_hi_and_version field)的前两个八位组。
  8. 将时间高位及版本字段的四位最显著位(bit 12 至 15)设置为第4.1.3节中指定的相应4位版本号。
  9. 将哈希值的第八个八位组赋给时钟序列高位及保留字段(clock_seq_hi_and_reserved field)。
  10. 将时钟序列高位及保留字段的两位最显著位(bit 6 和 7)分别设置为0和1。
  11. 将哈希值的第九个八位组赋给时钟序列低位字段(clock_seq_low field)。
  12. 将哈希值的第十至第十五个八位组赋给节点字段(node field)的前六个八位组。
  13. 最后,将生成的UUID转换成本地字节序。

五. 获取 UUID V4

    这里只介绍 V4 版本,因为 V4 是基于随机或者伪随机来实现的,只要保证保留位和版本号的固定,其他的随机生成就好。

5.1. 正则 + Math.random

利用 Math.random() 方法生成随机数。

function uuidv4() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = (Math.random() * 16 | 0), v = c == 'x' ? r : (r & 0b0011 | 0b1000);return v.toString(16);});
}

    先固定好格式,执行 replace,整体代码不难,唯一需要提一下的是 (r & 0b0011 | 0b1000) 操作,这里的作用就是设置保留位的值10。

r & 0b0011               // 高位,即2,3位 变为 00
r & 0b0011 | 0b1000      // 高位,即2,3位 变为 10

    举个例子, 用9为例,其二进制 0b1001 &

0b1001 & 0b0011   => 0b0011
0b0011 | 0b1000   => 0b1011

5.2. crypto.randomUUID

    现代浏览器也内置 Crypto: randomUUID() method , nodejs 15.6.0 版本以上就内置了crypto.randomUUID([options])

crypto.randomUUID()
// 4d93f326-3f48-4a43-929d-b6489f4754b5

5.3. URL.createObjectURL

function uuid() { const url = URL.createObjectURL(new Blob([])); // const uuid = url.split("/").pop(); const uid = url.substring(url.lastIndexOf('/')+ 1); URL.revokeObjectURL(url); return uid; 
}uuid()
// blob:http://localhost:3000/ff46f828-1570-4cc9-87af-3d600db71304

上面方式产生的都是 v4 版本,如果 v4 版本满足需求,就没有必要去引入第三方库了。

六. 后起之秀 NanoID

    Nano ID 是一个精巧高效的 JavaScript 库,用于生成短小、唯一且适合放在 URL 中的标识符字符串。这个工具提供了几个关键特性:

  1. 体积小巧:Nano ID 的最小化和压缩版本非常紧凑,大小仅为 116 字节。
  2. 安全性:该库使用硬件随机数生成器来确保生成的 ID 具有高安全性,可以在集群环境中安全使用。
  3. 短小 ID:相较于 UUID(通常包含 A-Z、a-z、0-9 以及 - 符号,共 36 个字符),Nano ID 使用了更大的字符集(包括 A-Za-z0-9_-),从而将 ID 的长度从 36 个符号减少到了 21 个,更便于在有限空间中使用。
  4. 可移植性:Nano ID 已被移植到超过 20 种编程语言中,具有良好的跨平台适用性。

Nano ID 和 UUID(Universally Unique Identifier)都是用于生成唯一标识符的机制,但它们之间存在一些关键差异:

  1. 长度与格式

    • UUID:标准 UUID 由32个十六进制数字组成,分为5组,每组之间用短横线-分隔,例如 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,总长度为36个字符(包括连字符)。
    • Nano ID:Nano ID 可配置长度,但默认生成的是较短的字符串,通常包含21个字符,并且可以自定义字符集(默认为 A-Za-z0-9_-)。
  2. 唯一性保证

    • UUID:基于时间戳、MAC 地址(对于v1 UUID)、随机数(对于v4 UUID)等多种因素生成,理论上全球范围内几乎不可能重复。
    • Nano ID:虽然也致力于生成唯一的 ID,但由于其较短的长度,在没有额外存储或算法保证的情况下,唯一性风险相对较大。不过,通过增大字符集和适当增加 ID 长度,Nano ID 也能实现很高的唯一性概率。
  3. 应用场景

    • UUID:广泛应用于数据库键、资源标识符、网络协议等需要全局唯一性的场景,尤其在网络间不同系统间的交互中常见。
    • Nano ID:更适合于对 ID 长度要求严格的场合,如 URL 友好、前端显示或者存储空间有限的情况。
  4. 性能与存储成本

    • UUID:由于较长的字符串长度,存储和传输时可能会占用更多空间。
    • Nano ID:因其短小,Nano ID 在存储和带宽消耗上更有优势。
  5. 安全性

    • UUID v4 是基于强随机性生成的,因此安全性较高,不易被预测。
    • Nano ID 也可以使用安全的随机源生成,同样能够达到较高的安全性,但在默认设置下,考虑到生成长度和字符集的选择,如果不在生成逻辑上做特殊处理以增加熵,其安全性可能不及 UUID。

    综上所述,选择 Nano ID 还是 UUID 取决于具体的应用需求,如果重视存储效率和简洁性,同时能接受合理的唯一性保证策略,则 Nano ID 可能更为合适;而在需要绝对唯一性和不考虑存储效率的场景下,UUID 往往是更好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/42768.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

管理《欧盟数字服务法》交易者要求

《数字服务法》合规性 根据《数字服务法》(DSA) 的要求,对于在欧盟地区 (EU) 通过 App Store 分发 App 的所有交易商,Apple 需要验证并显示其联系信息。请指明你是否将以交易商或非交易商的身份在欧盟地区分发任何内容。进一步了解你是否应为交易商。 …

[激光原理与应用-101]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 5 - 3C行业应用 - 电子布局类型

目录 前言: 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电子行业应用 二、3C电子行业中激光焊接 2.1 纽扣电池 2.2 均温板 2.3 指纹识别器 2.4 摄像头模组 2.5 IC芯片切割 三、3C行业中激光切割 四、激光在3C行业中的其他应用 4.1 涂层去除…

Golang | Leetcode Golang题解之第222题完全二叉树的节点个数

题目&#xff1a; 题解&#xff1a; func countNodes(root *TreeNode) int {if root nil {return 0}level : 0for node : root; node.Left ! nil; node node.Left {level}return sort.Search(1<<(level1), func(k int) bool {if k < 1<<level {return false}…

ubuntu22.04+pytorch2.3安装PyG图神经网络库

ubuntu下安装torch-geometric库&#xff0c;图神经网络 开发环境 ubuntu22.04 conda 24.5.0 python 3.9 pytorch 2.0.1 cuda 11.8 pyg的安装网上教程流传着许多安装方式&#xff0c;这些安装方式主要是&#xff1a;预先安装好pyg的依赖库&#xff0c;这些依赖库需要对应上pyth…

【Dison夏令营 Day 12】如何用 Python 构建数独游戏

通过本综合教程&#xff0c;学习如何使用 Pygame 在 Python 中创建自己的数独游戏。本指南涵盖安装、游戏逻辑、用户界面和计时器功能&#xff0c;是希望创建功能性和可扩展性数独益智游戏的爱好者的理想之选。 数独是一种经典的数字谜题&#xff0c;多年来一直吸引着谜题爱好…

昇思MindSpore25天学习Day19:CycleGAN图像风格迁移互换

(TOC)[CycleGAN图像风格迁移呼唤] 模型介绍 模型简介 CycleGAN(Cycle Generative Adversaial Network)即循环对抗生成网络&#xff0c;来自论文Link:Unpaired lmage-to-mage Translation using Cycle-Consistent AdvesairalNetworks该模型实现了—种在没有配对示例的情况下学…

从nginx返回404来看http1.0和http1.1的区别

序言 什么样的人可以称之为有智慧的人呢&#xff1f;如果下一个定义&#xff0c;你会如何来定义&#xff1f; 所谓智慧&#xff0c;就是能区分自己能改变的部分&#xff0c;自己无法改变的部分&#xff0c;努力去做自己能改变的&#xff0c;而不要天天想着那些无法改变的东西&a…

麒麟桌面操作系统上网络设置界面消失的解决方法

原文链接&#xff1a;麒麟桌面操作系统上网络设置界面消失的解决方法 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于麒麟桌面操作系统上网络设置界面消失解决方法的文章。在使用麒麟桌面操作系统时&#xff0c;可能会遇到网络设置界面突然消失的情况&#xff…

斯坦福CS224n深度学习培训营课程

自然语言处理领域的经典课程涵盖了从基础知识到最新研究的全面内容。本培训营将精选课程内容&#xff0c;结合实际案例和项目实践&#xff0c;带领学员深入探索自然语言处理的前沿&#xff0c;学习最先进的深度学习技术。 课程大小&#xff1a;2.6G 课程下载&#xff1a;http…

四自由度SCARA机器人的运动学和动力学matlab建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 针对SCARA 机器人系统进行了深入研究与探讨&#xff0c;提出SCARA机器人的动力学模型和运动学模型&#xff0c;并以MATLAB软件为仿真平台&#xff0c;通过MATLAB Robotics Too…

java核心-泛型

目录 概述什么是泛型分类泛型类泛型接口泛型方法 泛型通配符分类 泛型类型擦除分类无限制类型擦除有限制类型擦除 问题需求第一种第二种 概述 了解泛型有利于学习 jdk 、中间件的源码&#xff0c;提升代码抽象能力&#xff0c;封装通用性更强的组件。 什么是泛型 在定义类、接…

二手闲置平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;卖家管理&#xff0c;商品分类管理&#xff0c;商品信息管理&#xff0c;商品购买管理&#xff0c;商品配送管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;商品信息&a…

【linux服务器】大语言模型实战教程:LLMS大模型部署到个人服务器或嵌入式开发板(保姆级教学)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言 说到大语言模型相信大家都不会陌生&#xff0c;大型语言模型(LLMs)是人工智能文本处理的主要类型,也现在最流行的人工智能…

基于Java+SpringMvc+Vue技术智慧校园系统设计与实现--60页及以上论文参考

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

网络基础:园区网络架构

园区网络 园区网络&#xff08;Campus Network&#xff09;是指在一个相对较大的区域内&#xff0c;如大学校园、企业园区或政府机关等&#xff0c;建立的计算机网络系统。园区网络根据规模的不同&#xff0c;可以分为以下几种类型&#xff1a; ①小型园区网络&#xff1a;通常…

【系统架构设计师】八、系统工程基础知识(系统工程|系统性能)

目录 一、系统工程 1.1 系统工程的方法 1.1.1 霍尔的三维结构 1.1.2 切克兰德方法 1.1.3 并行工程方法 1.1.4 综合集成法 1.1.5.WSR 系统方法。 二、系统工程生命周期 2.1 系统工程生命周期7阶段 2.2 生命周期方法 三、基于模型的系统工程(MBSE) 四、系统性能 4.1…

vb.netcad二开自学笔记6:第一个绘制线段命令

.net编写绘制直线已完全不同于ActiveX的&#xff08;VBA&#xff09;的方式&#xff0c;过程更类似于arx程序&#xff0c;需要通过操作AutoCAD 数据库添加对象&#xff01;下面的代码是在以前代码基础上添加了一个新myline命令。 AutoCAD 数据库结构 myline命令代码 Imports A…

YoloV9改进策略:Block改进|轻量实时的重参数结构|最新改进|即插即用(全网首发)

摘要 本文使用重参数的Block替换YoloV9中的RepNBottleneck&#xff0c;GFLOPs从239降到了227&#xff1b;同时&#xff0c;map50从0.989涨到了0.99&#xff08;重参数后的结果&#xff09;。 改进方法简单&#xff0c;只做简单的替换就行&#xff0c;即插即用&#xff0c;非常…

使用ndoe实现自动化完成增删改查接口

使用ndoe实现自动化完成增删改查接口 最近工作内容比较繁琐&#xff0c;手里需要开发的项目需求比较多&#xff0c;常常在多个项目之间来回切换&#xff0c;有时候某些分支都不知道自己开发了什么、做了哪些需求&#xff0c; 使用手写笔记的方式去记录分支到头来也是眼花缭乱&a…

vscode调试教程

VSCode调试 VSCode Debuggers VSCode使用launch.json进行细粒度的控制&#xff0c;可以启动程序或将其附加到复杂的调试场景中 打开Run and Debug视图Ctrl Shift D 点击create a launch.json file&#xff0c;选择C(GDB/LLDB) 会在工作目录自动创建.vscode/launch.json文…