Q:怎样在Go语言中简单并快速地生成固定长度的随机字符串?

A:

问题是“最快和最简单的方式”,接下来我们会一步步迭代,最终实现最快的方式。每次迭代的基准测试代码放在了答案的末尾。

XX_test.go

前言

如果您只需要一个随机字符串,最快的解决方案不是首选解决方案。Paul的解决方案(下面的第一种方法)就很好。如果很关注性能,那么前两个方法可能是可接受的折中方案:它们把性能提升了50%,而且也没有显著增加复杂性。

话虽如此,但就算你不需要最快生成随机字符串的方法,通读这篇回答相信你也应该会有所收获。

Improvements

1. Genesis (Runes)

提醒一下,下面这个方法是我们用来改进的原始通用解决方案:

2. Bytes

如果要生成的随机字符串只包含大小写英文字母,那么我们可以只使用英文字母字节,因为英文字母和UTF8编码的字节是一一对应的(这就是Go存储字符串的方式)

所以可以这么写:

或者更好的写法是:

len(letters)len(s)

所以我们的第二种方法是这样的:

3. Remainder

rand.Intn()rand.Intn()Rand.Intn()Rand.Int31n()
rand.Int63()
rand.Int63()lenletterBytes)
rand.Int63()1<<63 - 1

解释下上面字母出现概率不相等的现象:假设你要生成一个0..5之间的随机数,如果使用3个随机位,那么会导致产生数字0..1范围内的概率是2..5的两倍;如果使用5个随机位,那么产生0..1范围的数字概率是6/32, 2..5范围的概率位5/32,这已经很接近了。增加位数可以使概率差异越来越小,当达到63位时,差异已经可以忽略不计了。

4. Masking

52=110100brand.Int63()0...len(letterBytes)-1
len(letterBytes)pow(0.5, n)

所以,这个解决方法是这样的:

5. Masking Improved

前面的解决方案只使用 rand.Int63() 返回的 63 个随机位中的最低 6 位。这是一种浪费,因为获取随机位是我们算法中最慢的部分。

因为我们有52个字母,可以用6位来编码一个字母索引。所以63个随机位可以生成63/6=10个不同的索引:

6. Source

上面的 Masking Improved 方法已经非常好了,我们几乎没办法做更多的改进。可以但没必要。

现在让我们找找其他可以改进的地方:随机数的来源。

crypto/randRead(b []byte)
math/randrand.Randrand.SourceInt63() int64
rand.Randrand.Source
math/randRand

还有就是:math/rand 的包文档说明

The default Source is safe for concurrent use by multiple goroutines.(协程安全)

rand.NewSource()rand.NewSource()

7. Utilizing strings.Builder

[]rune[]bytestring
strings.Builderbytes.Buffer[]byteBuilder.String()
strings.Builder

8. "Mimicing" strings.Builder with package unsafe

strings.Builder
strings.Builder
[]bytestring

Benchmark

BenchmarkRunes-4                     2000000    723 ns/op   96 B/op   2 allocs/op
BenchmarkBytes-4                     3000000    550 ns/op   32 B/op   2 allocs/op
BenchmarkBytesRmndr-4                3000000    438 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMask-4                 3000000    534 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImpr-4            10000000    176 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrc-4         10000000    139 ns/op   32 B/op   2 allocs/op
BenchmarkBytesMaskImprSrcSB-4       10000000    134 ns/op   16 B/op   1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4   10000000    115 ns/op   16 B/op   1 allocs/op