在 Go 中将 UUID 输出为短字符串
Output UUID in Go as a short string
是否有内置方式或合理的标准包允许您将标准 UUID 转换为短字符串以启用更短的 URL?
即利用使用更大范围的字符(例如 [A-Za-z0-9]
来输出更短的字符串。
我知道我们可以使用 base64 对字节进行编码,如下所示,但我正在寻找创建看起来像 "word" 的字符串的东西,即没有 +
和 /
:
id = base64.StdEncoding.EncodeToString(myUuid.Bytes())
一个universally unique identifier (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 编码(用 1 个符号编码 5 位,而十六进制编码用 1 个符号编码 4 位):
fmt.Println(base32.StdEncoding.EncodeToString(uuid))
// Output: 26 chars
CI7EKZ7ITMJNHJCWIJTFKRAAAA======
Trim 传输时尾随 =
符号,所以这将始终是 26 个字符。请注意,您必须在使用 base32.StdEncoding.DecodeString()
.
解码字符串之前附加 "======"
如果这对您来说还是太长,您可以使用 base64 编码(用 1 个符号编码 6 位):
fmt.Println(base64.RawURLEncoding.EncodeToString(uuid))
// Output: 22 chars
Ej5FZ-ibEtOkVkJmVUQAAA
请注意,base64.RawURLEncoding
生成一个 base64 字符串(无填充),对于 URL 包含是安全的,因为符号 table 中的 2 个额外字符(超出 [0-9a-zA-Z]
) 是 -
和 _
,它们都可以安全地包含在 URL 中。
不幸的是,base64 字符串可能包含超出 [0-9a-zA-Z]
的 2 个额外字符。请继续阅读。
已解释、转义的字符串
如果您不熟悉这 2 个额外的字符,您可以选择将 base64 字符串转换为 解释的转义字符串,类似于 Go 中的解释字符串文字。例如,如果要在解释的字符串文字中插入反斜杠,则必须将其加倍,因为反斜杠是表示序列的特殊字符,例如:
fmt.Println("One backspace: \") // Output: "One backspace: \"
我们可能会选择做类似的事情。我们必须指定一个特殊字符:9
.
推理: base64.RawURLEncoding
使用字符集:A..Za..z0..9-_
,所以9
代表最高位的代码为字母数字字符(61 十进制 = 111101b)。请参阅下面的优势。
因此,每当 base64 字符串包含 9
时,将其替换为 99
。每当 base64 字符串包含额外字符时,使用 sequence 代替它们:
9 => 99
- => 90
_ => 91
这是一个简单的替换 table,可以通过值 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
捕获的反向解码 table:
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 上的所有示例。
如建议的那样,如果您只想将一个相当随机的字符串用作 slug,最好根本不要理会 UUID。
您可以简单地使用 go 的原生 math/rand 库来生成所需长度的随机字符串:
import (
"math/rand"
"encoding/hex"
)
b := make([]byte, 4) //equals 8 characters
rand.Read(b)
s := hex.EncodeToString(b)
另一种选择是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
是否有内置方式或合理的标准包允许您将标准 UUID 转换为短字符串以启用更短的 URL?
即利用使用更大范围的字符(例如 [A-Za-z0-9]
来输出更短的字符串。
我知道我们可以使用 base64 对字节进行编码,如下所示,但我正在寻找创建看起来像 "word" 的字符串的东西,即没有 +
和 /
:
id = base64.StdEncoding.EncodeToString(myUuid.Bytes())
一个universally unique identifier (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 编码(用 1 个符号编码 5 位,而十六进制编码用 1 个符号编码 4 位):
fmt.Println(base32.StdEncoding.EncodeToString(uuid))
// Output: 26 chars
CI7EKZ7ITMJNHJCWIJTFKRAAAA======
Trim 传输时尾随 =
符号,所以这将始终是 26 个字符。请注意,您必须在使用 base32.StdEncoding.DecodeString()
.
"======"
如果这对您来说还是太长,您可以使用 base64 编码(用 1 个符号编码 6 位):
fmt.Println(base64.RawURLEncoding.EncodeToString(uuid))
// Output: 22 chars
Ej5FZ-ibEtOkVkJmVUQAAA
请注意,base64.RawURLEncoding
生成一个 base64 字符串(无填充),对于 URL 包含是安全的,因为符号 table 中的 2 个额外字符(超出 [0-9a-zA-Z]
) 是 -
和 _
,它们都可以安全地包含在 URL 中。
不幸的是,base64 字符串可能包含超出 [0-9a-zA-Z]
的 2 个额外字符。请继续阅读。
已解释、转义的字符串
如果您不熟悉这 2 个额外的字符,您可以选择将 base64 字符串转换为 解释的转义字符串,类似于 Go 中的解释字符串文字。例如,如果要在解释的字符串文字中插入反斜杠,则必须将其加倍,因为反斜杠是表示序列的特殊字符,例如:
fmt.Println("One backspace: \") // Output: "One backspace: \"
我们可能会选择做类似的事情。我们必须指定一个特殊字符:9
.
推理: base64.RawURLEncoding
使用字符集:A..Za..z0..9-_
,所以9
代表最高位的代码为字母数字字符(61 十进制 = 111101b)。请参阅下面的优势。
因此,每当 base64 字符串包含 9
时,将其替换为 99
。每当 base64 字符串包含额外字符时,使用 sequence 代替它们:
9 => 99
- => 90
_ => 91
这是一个简单的替换 table,可以通过值 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
捕获的反向解码 table:
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 上的所有示例。
如建议的那样
您可以简单地使用 go 的原生 math/rand 库来生成所需长度的随机字符串:
import (
"math/rand"
"encoding/hex"
)
b := make([]byte, 4) //equals 8 characters
rand.Read(b)
s := hex.EncodeToString(b)
另一种选择是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