RandStringBytesMaskImprSrc ) RandStringRunes()
I want a random string of characters only (uppercase or lowercase), no numbers in Golang. What is the fastest and simplest way to do this in Go?
Paul's solution provides a simple, general solution.
The question asks for the "the fastest and simplest way". Let's address this. We'll arrive at our final, fastest code in an iterative manner. Benchmarking each iteration can be found at the end of the answer.
XX_test.gogo test -bench .
I. Improvements
1. Genesis (Runes)
As a reminder, the original, general solution we're improving is this:
func init() {
rand.Seed(time.Now().UnixNano())
}
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)
}
2. Bytes
If the characters to choose from and assemble the random string contains only the uppercase and lowercase letters of the English alphabet, we can work with bytes only because the English alphabet letters map to bytes 1-to-1 in the UTF-8 encoding (which is how Go stores strings).
So instead of:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
we can use:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Or even better:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
conststringlen(letters)constlen(s)s
string
Our next destination looks like this:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
3. Remainder
rand.Intn()Rand.Intn()Rand.Int31n()
rand.Int63()
rand.Int63()len(letterBytes)
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
}
return string(b)
}
rand.Int63()521<<63 - 1
0..50..12..50..16/322..55/32
4. Masking
52 = 110100brand.Int63()0..len(letterBytes)-1
len(letterBytes)0.50.25npow(0.5, n)(64-52)/64 = 0.191e-8
So here is the solution:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
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. Masking Improved
rand.Int63()
63/6 = 10
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
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)
}
6. Source
The Masking Improved is pretty good, not much we can improve on it. We could, but not worth the complexity.
Now let's find something else to improve. The source of random numbers.
crypto/randRead(b []byte)crypto/rand
math/randrand.Randrand.Sourcerand.SourceInt63() int64
rand.Randrandrand.Source
var src = rand.NewSource(time.Now().UnixNano())
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
Randmath/randrand.Source
math/rand
The default Source is safe for concurrent use by multiple goroutines.
Sourcerand.NewSource()rand.NewSource()Source
rand.Read()
math.Read()Rand.Read()
There is one small "problem" with this: how many bytes do we need? We could say: as many as the number of output letters. We would think this is an upper estimation, as a letter index uses less than 8 bits (1 byte). But at this point we are already doing worse (as getting the random bits is the "hard part"), and we're getting more than needed.
rand
math.Rand()letterIdxBitsnn * letterIdxBits / 8.0github.com/icza/bitio
But Benchmark code still shows we're not winning. Why is it so?
rand.Read()Source.Int63()RandStringBytesMaskImprSrc()RandStringBytesMaskImprSrc()RandStringBytesMaskImprSrc()rand.Sourcerand.Read()Rand.Read()rand.Read()
II. Benchmark
All right, let's benchmark the different solutions.
BenchmarkRunes 1000000 1703 ns/op
BenchmarkBytes 1000000 1328 ns/op
BenchmarkBytesRmndr 1000000 1012 ns/op
BenchmarkBytesMask 1000000 1214 ns/op
BenchmarkBytesMaskImpr 5000000 395 ns/op
BenchmarkBytesMaskImprSrc 5000000 303 ns/op
Just by switching from runes to bytes, we immediately have 22% performance gain.
rand.Intn()rand.Int63()
Masking (and repeating in case of big indices) slows down a little (due to repetition calls): -20%...
rand.Int63()
rand.Sourcerand.Rand
RandStringBytesMaskImprSrc()RandStringRunes()
这篇关于如何在golang中生成一个固定长度的随机字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!