在Go中将UUID输出为短字符串

13

有没有内置的方式或者比较标准的包能够将标准 UUID 转换成短字符串,以便生成更短的 URL?

也就是利用更大范围的字符集,例如 [A-Za-z0-9] 输出一个短字符串。

我知道可以使用 base64 对字节进行编码,如下所示,但我想找到创建类似于“单词”样式的字符串的方法,即没有 +/


```python import base64 encoded_uuid = base64.urlsafe_b64encode(uuid.bytes).rstrip(b'=') ```
id = base64.StdEncoding.EncodeToString(myUuid.Bytes())

1
也许可以使用Base32编码来避免非字母字符?因为只能编码128位信息,所以比较小。 - JimB
发现了这个软件包:https://github.com/taskcluster/slugid-go - molivier
使用 encoding/base64.NewEncoding 并禁用填充。 - thwd
1
我曾经使用过这个包:https://github.com/renstrom/shortuuid/ - Travis
3个回答

47
一个通用唯一标识符(UUID)是一个128位的值,即16字节。为了人类可读显示,许多系统使用十六进制文本和插入连字符字符的规范格式,例如:
123e4567-e89b-12d3-a456-426655440000

这个长度为16*2 + 4 = 36。你可以选择省略连字符,这样就变成了:
fmt.Printf("%x\n", uuid)
fmt.Println(hex.EncodeToString(uuid))

// Output: 32 chars
123e4567e89b12d3a456426655440000
123e4567e89b12d3a456426655440000

你可以选择使用base32编码(与十六进制编码相比,它将5个比特编码为1个符号,而不是4个比特):
fmt.Println(base32.StdEncoding.EncodeToString(uuid))

// Output: 26 chars
CI7EKZ7ITMJNHJCWIJTFKRAAAA======

在传输时去除尾部的=符号,这样总长度始终为26个字符。请注意,在使用base32.StdEncoding.DecodeString()解码字符串之前,必须附加"======"

如果这仍然太长,您可以使用base64编码(它将6位编码为1个符号):

fmt.Println(base64.RawURLEncoding.EncodeToString(uuid))

// Output: 22 chars
Ej5FZ-ibEtOkVkJmVUQAAA

请注意,base64.RawURLEncoding 生成的 base64 字符串(不带填充)适合于 URL 包含,因为符号表中的两个额外字符(除了 [0-9a-zA-Z])是 -_,这两个字符都可以安全地包含在 URL 中。
不幸的是,对于您来说,base64 字符串可能包含两个超出 [0-9a-zA-Z] 范围的额外字符。请继续阅读。

解释和转义的字符串

如果您对这两个额外字符感到陌生,您可以选择将 base64 字符串转换为类似于 Go 中解释字符串字面量的 解释和转义的字符串。例如,如果您想在解释字符串字面量中插入反斜杠,则必须将其加倍,因为反斜杠是指示序列的特殊字符,例如:
fmt.Println("One backspace: \\") // Output: "One backspace: \"

我们可以选择做类似的事情。我们必须指定一个特殊字符: 可以是9
推理: base64.RawURLEncoding使用字符集:A..Za..z0..9-_,因此9代表具有字母数字字符的最高代码(61十进制= 111101b)。见下面的优点。
因此,每当base64字符串包含9时,请将其替换为99。每当base64字符串包含额外的字符时,请使用序列代替它们:
9  =>  99
-  =>  90
_  =>  91

这是一个简单的替换表,可以通过值 strings.Replacer 进行捕获。
var escaper = strings.NewReplacer("9", "99", "-", "90", "_", "91")

并使用它:

fmt.Println(escaper.Replace(base64.RawURLEncoding.EncodeToString(uuid)))

// Output:
Ej5FZ90ibEtOkVkJmVUQAAA

这将略微增加长度,因为有时会使用2个字符的序列而不是1个字符,但收益是只使用 [0-9a-zA-Z] 字符,正如您所需。平均长度将少于1个额外字符:23个字符。公平交易。
逻辑:为简单起见,假设所有可能的uuid具有相等的概率(uuid不是完全随机的,因此并非如此,但让我们把这个问题放在一边,因为这只是一个估计)。最后一个base64符号永远不会是可替换字符(这就是为什么我们选择特殊字符为9而不是像A),21个字符可能会变成可替换序列。一个被替换的机会是3/64 = 0.047,因此平均来说,这意味着21 * 3/64 = 0.98个序列将1个字符变成2个字符的序列,因此这等于额外字符的数量。
要解码,请使用以下strings.Replacer捕获的反向解码表:
var unescaper = strings.NewReplacer("99", "9", "90", "-", "91", "_")

解码转义的base64字符串的示例代码:

fmt.Println("Verify decoding:")
s := escaper.Replace(base64.RawURLEncoding.EncodeToString(uuid))
dec, err := base64.RawURLEncoding.DecodeString(unescaper.Replace(s))
fmt.Printf("%x, %v\n", dec, err)

输出:

123e4567e89b12d3a456426655440000, <nil>

Go Playground上尝试所有的示例。


3
很美。我不知道base64.RawURLEncoding,那个用于替换横杠和下划线的一行代码对某些人来说肯定很有用。谢谢你详细的回答。 - Jay

12

此处所建议,如果您只需要一个相当随机的字符串作为 slug 使用,则最好根本不要使用 UUID。

您可以简单地使用 Go 的原生 math/rand 库生成所需长度的随机字符串:

import (
"math/rand"
"encoding/hex"
)


b := make([]byte, 4) //equals 8 characters
rand.Read(b) 
s := hex.EncodeToString(b)

4
谢谢分享,这可能对一些人有用。不过,这个问题是具体询问如何呈现UUID。 - Jay

3

另一个选项是 math/big。虽然 base64 使用恒定的22个字符输出,但是根据输入不同,math/big 可以降至2个字符。

package main

import (
   "encoding/base64"
   "fmt"
   "math/big"
)

type uuid [16]byte

func (id uuid) encode() string {
   return new(big.Int).SetBytes(id[:]).Text(62)
}

func main() {
   var id uuid
   for n := len(id); n > 0; n-- {
      id[n - 1] = 0xFF
      s := base64.RawURLEncoding.EncodeToString(id[:])
      t := id.encode()
      fmt.Printf("%v %v\n", s, t)
   }
}

结果:

AAAAAAAAAAAAAAAAAAAA_w 47
AAAAAAAAAAAAAAAAAAD__w h31
AAAAAAAAAAAAAAAAAP___w 18owf
AAAAAAAAAAAAAAAA_____w 4GFfc3
AAAAAAAAAAAAAAD______w jmaiJOv
AAAAAAAAAAAAAP_______w 1hVwxnaA7
AAAAAAAAAAAA_________w 5k1wlNFHb1
AAAAAAAAAAD__________w lYGhA16ahyf
AAAAAAAAAP___________w 1sKyAAIxssts3
AAAAAAAA_____________w 62IeP5BU9vzBSv
AAAAAAD______________w oXcFcXavRgn2p67
AAAAAP_______________w 1F2si9ujpxVB7VDj1
AAAA_________________w 6Rs8OXba9u5PiJYiAf
AAD__________________w skIcqom5Vag3PnOYJI3
AP___________________w 1SZwviYzes2mjOamuMJWv
_____________________w 7N42dgm5tFLK9N8MT7fHC7

https://golang.org/pkg/math/big


太棒了,谢谢分享! - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接