我只想要一个随机字符串(大写或小写) ),在 Go 中没有数字。最快、最简单的方法是什么?
问题寻求“最快、最简单”的方法。保罗的回应提供了一种简单的技巧。然而,我们也考虑一下“最快”的方面。我们将迭代地完善我们的代码,得出优化的解决方案。
1。创世(符文)
我们将优化的初始解决方案是:
<code class="go">import ( "math/rand" "time" ) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) }</code>
2.字节
如果用于随机字符串的字符仅限于大写和小写英文字母,我们可以使用字节,因为英文字母在 UTF-8 编码中将 1 对 1 映射到字节( Go 用于存储字符串)。
因此我们可以将:
<code class="go">var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")</code>
替换为:
<code class="go">var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")</code>
或者更好:
<code class="go">const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"</code>
这是一个重大改进,因为我们现在可以使用 const (Go 支持字符串常量,但不支持切片常量)。此外,表达式 len(letters) 也将是常数。
3.余数
之前的解决方案通过调用 rand.Intn() (委托给 Rand.Intn() 并进一步委托给 Rand.Int31n())来确定字母的随机数。
这比使用 rand.Int63() 生成具有 63 个随机位的随机数要慢。
因此我们可以简单地调用 rand.Int63() 并使用除以 len(letters) 后的余数:
<code class="go">func RandStringBytesRmndr(n int) string { b := make([]byte, n) for i := range b { b[i] = letters[rand.Int63() % int64(len(letters))] } return string(b) }</code>
这样速度更快,同时保持所有字母的概率分布相等(虽然失真可以忽略不计,但字母数量 52 远小于 1
4.掩码
我们可以通过仅使用足以表示字母数量的随机数的最低位来保持字母的均匀分布。对于 52 个字母,需要 6 位:52 = 110100b。因此,我们将仅使用 rand.Int63() 返回的数字的最低 6 位。
我们也仅“接受”落在 0..len(letterBytes)-1 范围内的数字。如果最低位较大,我们将丢弃并请求一个新数字。
<code class="go">const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits ) func RandStringBytesMask(n int) string { b := make([]byte, n) for i := 0; i < n; { if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i++ } } return string(b) }
5。屏蔽改进
之前的解决方案仅使用 rand.Int63() 中 63 个随机位中的最低 6 位。这是低效的,因为获取随机位是我们算法中最慢的部分。
由于我们有 52 个字母,因此 6 位编码一个字母索引。 63 个随机位可以指定 63/6 = 10 个不同的字母索引。让我们使用全部 10 个:
<code class="go">const ( letterIdxBits = 6 // 6 bits to represent a letter index letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) func RandStringBytesMaskImpr(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdxMax letters! for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; { if remain == 0 { cache, remain = rand.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] i-- } cache >>= letterIdxBits remain-- } return string(b) }</code>
6。来源
屏蔽改进非常有效。我们再考虑另一个方面:随机数的来源。
crypto/rand 包提供了 Read(b []byte) 函数。然而,这对性能没有帮助,因为 crypto/rand 实现了一个加密安全的伪随机数生成器,速度较慢。
所以我们将坚持使用 math/rand 包。 rand.Rand 使用 rand.Source 作为随机位的源。所以我们可以直接使用rand.Source:
<code class="go">import ( "math/rand" "time" ) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) }</code>
7.利用 strings.Builder
以前的解决方案返回首先在切片中构造的字符串(Genesis 中的 []rune 和随后的 []byte),然后转换为字符串。最终的转换需要复制切片内容,因为字符串值是不可变的。
Go 1.10 引入了 strings.Builder。这种新类型可用于构建类似于 bytes.Buffer 的字符串内容。它内部使用 []byte,不需要复制内容来生成字符串。
<code class="go">var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")</code>
8.使用包 unsafe
strings.Builder 来“模仿” strings.Builder 在内部 [] 字节中构建一个字符串,就像我们自己做的那样。因此,使用 strings.Builder 会带来一些开销,我们只是为了避免最终的复制而切换。
但是,我们也可以通过使用 package unsafe 来避免这种复制:
<code class="go">var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")</code>
以上是如何在 Go 中快速生成指定长度的随机字符串?的详细内容。更多信息请关注PHP中文网其他相关文章!