# 8种超简单的Golang生成随机字符串方式

01/08 10:34

## 前言

icza给出了8个方案，最简单的方法并不是最快的方法，它们各有优劣，末尾附上性能测试结果：

### 1. Runes

package approach1

import (
"fmt"
"math/rand"
"testing"
"time"
)

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randStr(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

func TestApproach1(t *testing.T) {
rand.Seed(time.Now().UnixNano())
fmt.Println(randStr(10))
}

func BenchmarkApproach1(b *testing.B) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

### 2. Bytes

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

package approach2

import (
"fmt"
"math/rand"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randStr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

func TestApproach2(t *testing.T) {
rand.Seed(time.Now().UnixNano())

fmt.Println(randStr(10))
}

func BenchmarkApproach2(b *testing.B) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

### 3. Remainder 余数

package approach3

import (
"fmt"
"math/rand"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randStr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Int63() % int64(len(letters))]
}
return string(b)
}

func TestApproach3(t *testing.T) {
rand.Seed(time.Now().UnixNano())

fmt.Println(randStr(10))
}

func BenchmarkApproach3(b *testing.B) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

package approach4

import (
"fmt"
"math/rand"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

const (
// 6 bits to represent a letters index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMask = 1 <<letterIdBits - 1
)

func randStr(n int) string {
b := make([]byte, n)
for i := range b {
if idx := int(rand.Int63() & letterIdMask); idx < len(letters) {
b[i] = letters[idx]
i++
}
}
return string(b)
}

func TestApproach4(t *testing.T) {
rand.Seed(time.Now().UnixNano())

fmt.Println(randStr(10))
}

func BenchmarkApproach4(b *testing.B) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

package approach5

import (
"fmt"
"math/rand"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

const (
// 6 bits to represent a letter index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMax  = 63 / letterIdBits
)

func randStr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdMax
}
if idx := int(cache & letterIdMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdBits
remain--
}
return string(b)
}

func TestApproach5(t *testing.T) {
rand.Seed(time.Now().UnixNano())

fmt.Println(randStr(10))
}

func BenchmarkApproach5(b *testing.B) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

### 6. Source

crypto/rand的包提供了Read(b []byte)的函数，可以通过这个函数获得需要的随机比特数，只需要一次调用。不过并不能提升性能，因为crypto/rand实现了一个密码学上的安全伪随机数，所以速度比较慢。

package approach6

import (
"fmt"
"math/rand"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

var src = rand.NewSource(time.Now().UnixNano())

const (
// 6 bits to represent a letter index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMax  = 63 / letterIdBits
)

func randStr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdMax letters!
for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdMax
}
if idx := int(cache & letterIdMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdBits
remain--
}
return string(b)
}

func TestApproach6(t *testing.T) {
fmt.Println(randStr(10))
}

func BenchmarkApproach6(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

默认的Source是协程安全的

### 7. 使用 strings.Builder

Go1.10引入了strings.Builder，这是一个新的类型，和bytes.Buffer类似，用来构造字符串。底层使用[]byte来构造内容，正是我们现在在做的，最后可以通过Builder.String()方法来获得最终的字符串值。但它很酷的地方在于，它无需执行刚才谈到的复制即可完成此操作。它敢这么做是因为它底层构造的[]byte从未暴露出来，所以仍然可以保证没有人可以无意地、恶意地来修改已经生成的不可变字符串。

package approach7

import (
"fmt"
"math/rand"
"strings"
"testing"
"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

var src = rand.NewSource(time.Now().UnixNano())

const (
// 6 bits to represent a letter index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMax  = 63 / letterIdBits
)

func randStr(n int) string {
sb := strings.Builder{}
sb.Grow(n)
// A rand.Int63() generates 63 random bits, enough for letterIdMax letters!
for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdMax
}
if idx := int(cache & letterIdMask); idx < len(letters) {
sb.WriteByte(letters[idx])
i--
}
cache >>= letterIdBits
remain--
}
return sb.String()
}

func TestApproach7(t *testing.T) {
fmt.Println(randStr(10))
}

func BenchmarkApproach7(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

### 8. “Mimicing” strings.Builder with package unsafe

string.Builder跟我们第六节地解法一样，都是用[]byte来构建字符串。切换到strings.Builder可能有一些太重了，我们使用strings.Builder只是想避免拷贝slice。

string.Builder使用unsafe包来避免最终的拷贝

// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}

package approach8

import (
"fmt"
"math/rand"
"testing"
"time"
"unsafe"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

var src = rand.NewSource(time.Now().UnixNano())

const (
// 6 bits to represent a letter index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMax  = 63 / letterIdBits
)

func randStr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdMax letters!
for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdMax
}
if idx := int(cache & letterIdMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}

func TestApproach8(t *testing.T) {
fmt.Println(randStr(10))
}

func BenchmarkApproach8(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = randStr(10)
}
}

## Benchmark

go test ./... -bench=. -benchmem

(译者注：第三列代表操作一次需要多少纳秒)

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

BenchmarkApproach1-12            3849038               299.5 ns/op            64 B/op          2 allocs/op
BenchmarkApproach2-12            5545350               216.4 ns/op            32 B/op          2 allocs/op
BenchmarkApproach3-12            7003654               169.7 ns/op            32 B/op          2 allocs/op
BenchmarkApproach4-12            7164259               168.7 ns/op            32 B/op          2 allocs/op
BenchmarkApproach5-12           13205474                89.06 ns/op           32 B/op          2 allocs/op
BenchmarkApproach6-12           13665636                84.41 ns/op           32 B/op          2 allocs/op
BenchmarkApproach7-12           17213431                70.37 ns/op           16 B/op          1 allocs/op
BenchmarkApproach8-12           19756956                61.41 ns/op           16 B/op          1 allocs/op

• 仅仅只是把rune切换到byte，就获得了性能的大幅度提升(大于百分之20)
• 使用rand.Int63()代替rand.Intn()也获得大幅度提升(大于百分之20)
• 不过使用了一次rand.Int63()返回的全部字符后，性能提升了3倍
• 使用rand.Source替代rand.Rand，性能提升了21%
• 使用strings.Builder，我们在速度上提升了3.5%，并且把原本2次的内存分配，降低到了一次！
• 使用unsafe包来代替strings.Builder，性能提升了14%

0 评论
1 收藏
0