模块 Random::Formatter

随机数格式化器。

以多种方式格式化生成的随机数。当需要 `random/formatter` 时,多个方法会添加到空的 core 模块 `Random::Formatter`,使其作为 Random 的实例和模块方法可用。

标准库 SecureRandom 也扩展了该模块,下面描述的方法在其中作为模块方法可用。

示例

生成随机十六进制字符串

require 'random/formatter'

prng = Random.new
prng.hex(10) #=> "52750b30ffbc7de3b362"
prng.hex(10) #=> "92b15d6c8dc4beb5f559"
prng.hex(13) #=> "39b290146bea6ce975c37cfc23"
# or just
Random.hex #=> "1aed0c631e41be7f77365415541052ee"

生成随机 base64 字符串

prng.base64(10) #=> "EcmTPZwWRAozdA=="
prng.base64(10) #=> "KO1nIU+p9DKxGg=="
prng.base64(12) #=> "7kJSM/MzBJI+75j8"
Random.base64(4) #=> "bsQ3fQ=="

生成随机二进制字符串

prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43"

生成字母数字字符串

prng.alphanumeric(10) #=> "S8baxMJnPl"
prng.alphanumeric(10) #=> "aOxAg8BAJe"
Random.alphanumeric #=> "TmP9OsJHJLtaZYhP"

生成 UUID

prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd"

所有方法在标准库 SecureRandom 中也可用

SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf"

常量

ALPHANUMERIC

alphanumeric 的默认字符列表。

公共实例方法

alphanumeric(n = nil, chars: ALPHANUMERIC) 点击以切换源代码

生成一个随机字母数字字符串。

参数 *n* 指定要生成的字母数字字符串的长度(以字符为单位)。参数 *chars* 指定结果由哪些字符组成的字符列表。

如果未指定 *n* 或 *n* 为 nil,则假定为 16。未来可能会更大。

除非指定了 *chars*,否则结果可能包含 A-Z、a-z 和 0-9。

require 'random/formatter'

Random.alphanumeric     #=> "2BuBuLf3WfSKyQbR"
# or
prng = Random.new
prng.alphanumeric(10) #=> "i6K93NdqiH"

Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952"
# or
prng = Random.new
prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''."
# File random/formatter.rb, line 367
def alphanumeric(n = nil, chars: ALPHANUMERIC)
  n = 16 if n.nil?
  choose(chars, n)
end
base64(n=nil) 点击以切换源代码

生成随机 base64 字符串。

参数 *n* 指定要生成的随机数的长度(以字节为单位)。结果字符串的长度约为 *n* 的 4/3。

如果未指定 *n* 或 *n* 为 nil,则假定为 16。未来可能会更大。

结果可能包含 A-Z、a-z、0-9、“+”、“/” 和 “=”。

require 'random/formatter'

Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
# or
prng = Random.new
prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="

有关 base64 的定义,请参阅 RFC 3548。

# File random/formatter.rb, line 114
def base64(n=nil)
  [random_bytes(n)].pack("m0")
end
hex(n=nil) 点击以切换源代码

生成随机十六进制字符串。

参数 *n* 指定要生成的随机数的长度(以字节为单位)。生成的十六进制字符串的长度为 *n* 的两倍。

如果未指定 *n* 或 *n* 为 nil,则假定为 16。未来可能会更大。

结果可能包含 0-9 和 a-f。

require 'random/formatter'

Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
# or
prng = Random.new
prng.hex #=> "91dc3bfb4de5b11d029d376634589b61"
# File random/formatter.rb, line 92
def hex(n=nil)
  random_bytes(n).unpack1("H*")
end
random_bytes(n=nil) 点击以切换源代码

生成随机二进制字符串。

参数 *n* 指定结果字符串的长度。

如果未指定 *n* 或 *n* 为 nil,则假定为 16。未来可能会更大。

结果可能包含任何字节:“x00” - “xff”。

require 'random/formatter'

Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
# or
prng = Random.new
prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
# File random/formatter.rb, line 71
def random_bytes(n=nil)
  n = n ? n.to_int : 16
  gen_random(n)
end
urlsafe_base64(n=nil, padding=false) 点击以切换源代码

生成随机的 URL 安全 base64 字符串。

参数 *n* 指定要生成的随机数的长度(以字节为单位)。结果字符串的长度约为 *n* 的 4/3。

如果未指定 *n* 或 *n* 为 nil,则假定为 16。未来可能会更大。

布尔参数 *padding* 指定填充。如果为 false 或 nil,则不生成填充。否则,生成填充。默认情况下,不生成填充,因为 “=” 可能用作 URL 分隔符。

结果可能包含 A-Z、a-z、0-9、“-” 和 “_”。如果 *padding* 为 true,则也会使用 “=”。

require 'random/formatter'

Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
# or
prng = Random.new
prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"

prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="

有关 URL 安全 base64 的定义,请参阅 RFC 3548。

# File random/formatter.rb, line 145
def urlsafe_base64(n=nil, padding=false)
  s = [random_bytes(n)].pack("m0")
  s.tr!("+/", "-_")
  s.delete!("=") unless padding
  s
end
uuid() 点击以切换源代码

生成随机 v4 UUID(通用唯一标识符)。

require 'random/formatter'

Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
# or
prng = Random.new
prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"

版本 4 UUID 是纯随机的(版本除外)。它不包含诸如 MAC 地址、时间戳等有意义的信息。

结果包含 122 个随机位(15.25 个随机字节)。

有关 UUIDv4 的详细信息,请参阅 RFC9562

# File random/formatter.rb, line 169
def uuid
  ary = random_bytes(16)
  ary.setbyte(6, (ary.getbyte(6) & 0x0f) | 0x40)
  ary.setbyte(8, (ary.getbyte(8) & 0x3f) | 0x80)
  ary.unpack("H8H4H4H4H12").join(?-)
end
也别名为:uuid_v4
uuid_v4()
别名:uuid
uuid_v7(extra_timestamp_bits: 0) 点击以切换源代码

生成随机 v7 UUID(通用唯一标识符)。

require 'random/formatter'

Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e"
Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5"
Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23"
Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31"
#                    |<--sorted-->| |<----- random ---->|

# or
prng = Random.new
prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98"

版本 7 UUID 从 64 位 Unix 时间戳(自 epoch 以来的毫秒数)的最低有效 48 位开始,并用随机数据填充剩余位,不包括版本和变体位。

这允许按创建时间对版本 7 UUID 进行排序。时间排序的 UUID 可用于更好地进行新插入记录的数据库索引局部性,与随机数据插入相比,这可能具有显着的性能优势。

结果包含 74 个随机位(9.25 个随机字节)。

请注意,此方法无法重现,因为其输出不仅包含随机位,还包含时间戳。

有关 UUIDv7 的详细信息,请参阅 RFC9562

单调性

UUIDv7 默认具有毫秒精度,因此在同一毫秒内创建的多个 UUID 不会按单调递增的顺序发出。要创建具有亚毫秒精度的时间排序 UUID,可以使用 `extra_timestamp_bits` 添加最多 12 位的额外时间戳。额外的时间戳精度以随机位为代价。设置 `extra_timestamp_bits: 12` 可提供 ~244ns 的精度,但仅有 62 个随机位(7.75 个随机字节)。

prng = Random.new
Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) }
# =>
["0188d4c7-13da-74f9-8b53-22a786ffdd5a",
 "0188d4c7-13da-753b-83a5-7fb9b2afaeea",
 "0188d4c7-13da-754a-88ea-ac0baeedd8db",
 "0188d4c7-13da-7557-83e1-7cad9cda0d8d"]
# |<--- sorted --->| |<-- random --->|

Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) }
# =>
["0188d4c7-3333-7a95-850a-de6edb858f7e",
 "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9",  # <- out of order
 "0188d4c7-3333-7ae2-995a-9f135dc44ead",  # <- out of order
 "0188d4c7-3333-7af9-87c3-8f612edac82e"]
# |<--- sorted -->||<---- random --->|

任何系统时钟回滚都会破坏单调性。UUIDv7 基于 UTC,它不包括闰秒,并且可以回滚时钟。为避免这种情况,系统时钟可以与配置为使用 “闰秒抹平” 方法的 NTP 服务器同步。还需要 NTP 或 PTP 来跨分布式节点同步。

没有实现用于更严格地保证单调性的计数器和其他机制。具有更严格要求的应用程序应遵循规范的 第 6.2 节

# File random/formatter.rb, line 246
def uuid_v7(extra_timestamp_bits: 0)
  case (extra_timestamp_bits = Integer(extra_timestamp_bits))
  when 0 # min timestamp precision
    ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
    rand = random_bytes(10)
    rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version
    rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant
    "%08x-%04x-%s" % [
      (ms & 0x0000_ffff_ffff_0000) >> 16,
      (ms & 0x0000_0000_0000_ffff),
      rand.unpack("H4H4H12").join("-")
    ]

  when 12 # max timestamp precision
    ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
      .divmod(1_000_000)
    extra_bits = ns * 4096 / 1_000_000
    rand = random_bytes(8)
    rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant
    "%08x-%04x-7%03x-%s" % [
      (ms & 0x0000_ffff_ffff_0000) >> 16,
      (ms & 0x0000_0000_0000_ffff),
      extra_bits,
      rand.unpack("H4H12").join("-")
    ]

  when (0..12) # the generic version is slower than the special cases above
    rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN")
    rand_mask_bits = 12 - extra_timestamp_bits
    ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
      .divmod(1_000_000)
    "%08x-%04x-%04x-%04x-%04x%08x" % [
      (ms & 0x0000_ffff_ffff_0000) >> 16,
      (ms & 0x0000_0000_0000_ffff),
      0x7000 |
        ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) |
        rand_a & ((1 << rand_mask_bits) - 1),
      0x8000 | (rand_b1 & 0x3fff),
      rand_b2,
      rand_b3
    ]

  else
    raise ArgumentError, "extra_timestamp_bits must be in 0..12"
  end
end

私有实例方法

choose(source, n) 点击以切换源代码

生成一个随机从字符源数组中提取的字符串。

参数 *source* 指定要从中生成字符串的字符数组。参数 *n* 指定要生成的字符串的长度(以字符为单位)。

结果可能包含源数组中的任何字符。

require 'random/formatter'

prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
prng.choose([*'0'..'9'], 5)  #=> "27309"
# File random/formatter.rb, line 312
        def choose(source, n)
  size = source.size
  m = 1
  limit = size
  while limit * size <= 0x100000000
    limit *= size
    m += 1
  end
  result = ''.dup
  while m <= n
    rs = random_number(limit)
    is = rs.digits(size)
    (m-is.length).times { is << 0 }
    result << source.values_at(*is).join('')
    n -= m
  end
  if 0 < n
    rs = random_number(limit)
    is = rs.digits(size)
    if is.length < n
      (n-is.length).times { is << 0 }
    else
      is.pop while n < is.length
    end
    result.concat source.values_at(*is).join('')
  end
  result
end
gen_random(n) 点击以切换源代码

Random 的内部接口;生成 *n* 字节的随机数据。

# File random/formatter.rb, line 294
        def gen_random(n)
  self.bytes(n)
end