用Racket语言生成全球唯一标志(GUID)

78 篇文章 16 订阅
1 篇文章 0 订阅

​1 什么是GUID

 在百度百科里解释如下:

全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID 的总数达到了2^128(3.4×10^38)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。

GUID的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”(即8-4-4-4-12的结构);其中每个x是0-9或a-f范围内的一个十六进制数。例如:6F9619FF-8B86-D011-B42D-00C04FC964FF 即为有效的 GUID 值。

2 GUID的特点

1、需要GUID的时候,可以完全由算法自动生成,不需要一个权威机构来管理。

2、GUID理论上能产生全宇宙唯一的值,对于以后的数据导入很方便。

3 GUID在BIM中的价值

对与BIM(Building Information Modeling,建筑信息模型化)来说,IFC更有其特殊意义。在IFC(Industry Foundation Classes,工业基础类)中作为对象的唯一标志值,无论是核心层、核心层、共享层还是专业领域层,都被广泛使用。在我们国家标准《工业基础类平台规范》(GB/T 25507——2010)附录A中专门提供了《全球唯一标识C语言代码》。

但遗憾的是,该附录并未给出编程的逻辑思路,给出的代码其实也并不清晰(个人认为),可读性不高。另外,里边生成GUID的核心函数是Windows系统的CoCreateGuid函数(在系统DLL库ole32中),如果用这个代码生成,就直接限制了程序跨平台的能力。

因此,我尝试用Racket语言来实现GUID的程序,同时介绍实现思路,希望能达到抛砖引玉的效果。

4 简单生成GUID

这里我们将GUID的每一段都通过随机生成来实现,由于生成的随机性很大,总数达到了3.4×10^38个,相同的几率非常小。听说有人查看Win的源代码,那个神奇的函数的每段都是随机函数生成的(^-^)。

Racket里生成随机数的常见函数为"random",它返回一个范围为0~k-1的随机精确整数。

不过还有一个函数——"crypto-random-bytes",它在这里更适合使用。它返回n个随机字节。在Unix系统上,字节是从“/dev/urandom”获得,而在Windows上则使用RtlGenRand系统函数获得。取得随机数后,通过"bytes->hex-string"(来自于库“file/sha1”,因此需要加上需求(require file/sha1))直接转换成16进制字符串。

这个思路很直接,实现起来简单。我们直接上代码:

(define (create-guid-string-by-random)
  (string-append
   (bytes->hex-string (crypto-random-bytes 4)) "-"
   (bytes->hex-string (crypto-random-bytes 2)) "-"
   (bytes->hex-string (crypto-random-bytes 2)) "-"
   (bytes->hex-string (crypto-random-bytes 2)) "-"
   (bytes->hex-string (crypto-random-bytes 6))))

前面说过,这种方式随机生成两个相同GUID的可能性非常小,但并不为0。但并不为0!(估计遇到相同的时候一定是前无古人的巨额大奖)。但是为防万一,这个也是要避免的,毕竟在很多场景下,即使出现一次,造成的问题也是无法想象的。

5 更可靠的生成方案

为了进一步减少重复的可能性,我们将随机的范围缩小。这里从两方面来实现这一点:

1、限定特定时间:在特定时间里去生成随机数,即使生成了相同的随机数,但加上时间限制,也是不同的。

特定时间有日期(date)和时间(time)两个方面。我们可以将其分别嵌入到GUID中。

2、限定程序运行的主机相关信息。在不同的机器上即使生成了相同随机数,也是不同的。主机相关信息有:主板标记信息、CMOS标记信息、芯片标记信息、IP地址、MAC地址、主机名称标记。另外还有一种,就是程序的进程标志。我们这里就用这个来生成。

由此,我们将日期的年月日、时间、程序的进程标志号分别转换成16进制字符串嵌入到GUID里,剩下的通过随机数生成。具体为:将年转换为第一段8位,月日转换为第二段4位,时、分、秒转换为第三段4位,进程标志转换为第四段4位,第五段12位全部由随机生成。

5.1 用日期时间生成数据(第一、二、三段)

Racket的"current-date"函数取得当前系统时间(时间分分秒秒向前走,不停地创造新数据),它返回一个时间结构值。

时间结构的定义如下:

(struct date
  (second
   minute
   hour
   day
   month
   year
   week-day
   year-day
   dst?
   time-zone-offset))

由此可以看出,我们可以直接从取得的时间结构值里取得我们想要的数据,根据上边的思路转换成第一至三段的内容。如下:

(define (guid-data-by-date)
 (let* ([dt (current-date)]
        [y (date-year dt)]
        [ys (~a y #:width 4)]
        [m (date-month dt)]
        [d (date-day dt)]
        [h (date-hour dt)]
        [min (date-minute dt)]
        [sec (date-second dt)])
    (string-append
     (bytes->hex-string (string->bytes/locale ys)) "-"
     (bytes->hex-string (bytes m d)) "-"
     (bytes->hex-string (bytes (+ min h) (+ sec h))))))

以上我们用Racket函数"bytes->hex-string"将字节转换为16进制字符串(每个字节转换为2位)。"bytes"函数用于返回新的可变字节字符串,"string-append"将给定的字符串连接起来。

这里有两个地方需要说明:

  • 一是对年的处理。由于年的十进制位数长度不是定长的,这里统一转换(限定)为4位(在9999年之后会出现与之前重复的年数值)。

  • 二是对时间的时的处理。为了将时、分、秒限定在4位16进制数里,这里在分、秒的数值上加上时的数值,这样生成2个数,转换出来则为4位16进制数。

5.2 用进程标志生成数据(第四段)

Racket的"getpid"函数取得一个标识系统当前进程的整数值。我们将这个整数值分成两部分,每一部分转换成2位16进制数的字符串即可。如下:

(define (guid-data-by-process)
  (let ([pid (getpid)])
    (bytes->hex-string (bytes (quotient pid 100)
                              (remainder pid 100)))))

5.3 用随机数生成数据(第五段)

同上。如下:

(define (guid-data-by-random)
  (bytes->hex-string (crypto-random-bytes 6)))

最后,将以上生成的各段16进制字符串连起来就生成了GUID的字符串码:

(define (create-guid-string)
  (string-append
   (guid-data-by-date) "-"
   (guid-data-by-process) "-"
   (guid-data-by-random)))

在Windows中,GUID的字符串表示形式实际上还包括两边的大括号(“{}”)。我们可以通过"string->guid"函数将GUID字符串转换成GUID值,也可以通过"guid->string"函数将GUID值转换为GUID字符串。这两个函数需要"(require ffi/com)"支持。

(以上内容由Racket语言的Scribble编辑生成)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值