Generate a random string of a fixed length
https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
最快: Mimicing" strings.Builder with package unsafe Go playground: https://go.dev/play/p/fQeQ0KEQk2M
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
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
)
1. Genesis (Runes)
一般解法, 可以用中文
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
如果只是要隨機選擇英文字母的大小寫可以用byte, 因為英文字母在UTF-8 encoding的mapping是 1 to 1.
一個 rune 佔用 4 bytes. 一個 byte 佔用 1 byte
一個中文字佔用 3 bytes.
將1. Genesis (Runes)取代掉:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
we can use:
var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Or even better: const通常會被編譯器在預處理階段直接展開,作為指令數據使用
const letters = "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
這效能會比2. Bytes明顯更快,缺點是所有字母的機率不會完全相同 (假設 rand.Int63() 以相等的機率產生所有 63 位數字). 儘管由於字母 52(a-zA-Z) 的數量比 1<<63 - 1 小得多, 因此失真非常小,但實際上這完全沒問題.
Previous solutions get a random number to designate a random letter by calling rand.Intn() which delegates to >Rand.Intn() which delegates to Rand.Int31n().
This is much slower compared to rand.Int63() which produces a random number with 63 random bits.
So we could simply call rand.Int63() and use the remainder after dividing by 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)
}
4. Masking
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
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 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
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
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
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)
}
package doc of math/rand states:
The default Source is safe for concurrent use by multiple goroutines.
So the default source is slower than a
Sourcethat may be obtained byrand.NewSource(), because the default source has to provide safety under concurrent access / use, whilerand.NewSource()does not offer this (and thus theSourcereturned by it is more likely to be faster)
7. Utilizing strings.Builder
func RandStringBytesMaskImprSrcSB(n int) string {
sb := strings.Builder{}
sb.Grow(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) {
sb.WriteByte(letterBytes[idx])
i--
}
cache >>= letterIdxBits
remain--
}
return sb.String()
}
8. "Mimicing" strings.Builder with package unsafe (最快)
strings.Builder avoids the final copy by using package unsafe:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
func RandStringBytesMaskImprSrcUnsafe(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)(unsafe.Pointer(&b))
}
9. Using rand.Read()
Go 1.7 added a rand.Read() function and a Rand.Read() method. We should be tempted to use these to read as many bytes as we need in one step, in order to achieve better performance.
func RandStringRandRead(n int) string {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return "nil"
}
return fmt.Sprintf("%x", b)[:n]
}
10. Using rand.Read() Base64
func RandStringBase64String(l int) string {
buff := make([]byte, int(math.Ceil(float64(l)/float64(1.33333333333))))
rand.Read(buff)
str := base64.RawURLEncoding.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
11. Using rand.Read() Base16
func RandStringBase16String(l int) string {
buff := make([]byte, int(math.Ceil(float64(l)/2)))
rand.Read(buff)
str := hex.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
Benchmark
goos: darwin
goarch: amd64
pkg: MyGoNote/Utils/randomStringUtils
cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
BenchmarkRunes-8 2285336 601.4 ns/op 88 B/op 2 allocs/op
BenchmarkBytes-8 2669830 461.4 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-8 3254424 356.0 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-8 3478284 359.2 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-8 11620720 99.50 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-8 15264619 76.80 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-8 14946604 70.96 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-8 20539147 54.80 ns/op 16 B/op 1 allocs/op
BenchmarkRandStringRandRead-8 5039289 233.8 ns/op 72 B/op 3 allocs/op
BenchmarkRandStringBase64String-8 8887881 131.8 ns/op 64 B/op 3 allocs/op
BenchmarkRandStringBase16String-8 10199756 112.0 ns/op 40 B/op 3 allocs/op
PASS
ok MyGoNote/Utils/randomStringUtils 15.602s