如何在围棋中生成一个固定长度的随机字符串?

在Go中,我只想要一个随机字符串(大写或小写),没有数字。最快最简单的方法是什么?

340192 次浏览

两个可能的选项(当然可能有更多):

  1. 您可以使用crypto/rand包,它支持读取随机字节数组(从/dev/urandom),并面向加密随机生成。see http://golang.org/pkg/crypto/rand/#example_Read。它可能比普通的伪随机数生成慢。

  2. 取一个随机数,用md5或类似的方法进行哈希。

你可以为它编写代码。如果您希望使用UTF-8编码时所有字母都是单个字节,则此代码可以稍微简单一些。

package main


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


var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")


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


func main() {
rand.Seed(time.Now().UnixNano())


fmt.Println(randSeq(10))
}

保罗的解决方案提供了一个简单的通用解决方案。

这个问题要求“最快最简单的方法”;。让我们解决最快部分。我们将以迭代的方式得到最终的、最快的代码。每次迭代的基准测试可以在答案的末尾找到。

所有的解决方案和基准测试代码都可以在去操场中找到。Playground上的代码是测试文件,而不是可执行文件。你必须将它保存到一个名为XX_test.go的文件中并运行它

go test -bench . -benchmem

前言:

如果您只需要一个随机字符串,那么最快的解决方案并不是最佳解决方案。为此,保罗的解决方案是完美的。这是在性能确实重要的情况下。虽然前两个步骤(字节剩余部分)可能是一个可以接受的折衷:它们确实提高了大约50%的性能(具体数字见2基准部分),而且它们不会显著增加复杂性。

话虽如此,即使你不需要最快的解决方案,通读这个答案可能是冒险的,也是有教育意义的。

我改进

1. 《创世纪》(诗歌)

提醒一下,我们正在改进的原始通用解决方案是:

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. 字节

如果要从随机字符串中选择和组合的字符只包含英文字母的大写字母和小写字母,那么我们只能使用字节,因为英文字母在UTF-8编码中映射为1对1的字节(这是Go存储字符串的方式)。

所以不要:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

我们可以用:

var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

或者更好:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

现在这已经是一个很大的改进:我们可以将其实现为const(有string常量,但有没有切片常数)。作为额外的增益,表达式len(letters)也将是const!(如果s是一个字符串常量,表达式len(s)就是常量。)

代价是什么?什么都没有。strings可以被索引,索引它的字节,完美,正是我们想要的。

我们的下一个目的地是这样的:

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.剩余部分

以前的解决方案通过调用rand.Intn()来获得一个随机数来指定一个随机字母,rand.Intn()委托给Rand.Intn(),后者委托给Rand.Int31n()

这比rand.Int63()慢得多,rand.Int63()产生一个带有63个随机位的随机数。

因此,我们可以简单地调用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()以相同的概率产生所有63位数字)。虽然由于52的字母数量比1<<63 - 1小很多,所以失真非常小,所以在实践中这是完全没问题的。

为了更容易理解:假设你想要一个0..5范围内的随机数。使用3个随机位,这将产生数字0..1的概率是范围2..5的两倍。使用5个随机位,0..1范围内的数字将以6/32的概率出现,而2..5范围内的数字则以5/32的概率出现,现在更接近所需的概率。增加比特数使其不那么重要,当达到63位时,它可以忽略不计。

4. 屏蔽

在前面的解决方案的基础上,我们可以通过只使用随机数的最低位数来保持字母的均匀分布。例如,如果我们有52个字母,它需要6位来表示:52 = 110100b。因此,我们将只使用rand.Int63()返回的数字的最低6位。为了保持信件的平均分配,我们只“接受”;如果它在0..len(letterBytes)-1范围内,则返回该数字。如果最低位更大,则丢弃它并查询一个新的随机数。

注意,最低位大于或等于len(letterBytes)的概率一般小于0.5(平均为0.25),这意味着即使是这种情况,重复这种“罕见”;Case减少了找不到一个好数字的机会。在n重复之后,我们仍然没有一个好的索引的机会比pow(0.5, n)要小得多,这只是一个上估计。在52个字母的情况下,最低的6位不好的概率仅为(64-52)/64 = 0.19;这意味着,例如,重复10次后没有得到一个好数字的几率是1e-8

这就是解决方案:

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. 掩蔽改进

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

如果我们有52个字母,这意味着6位编码一个字母索引。因此,63个随机位可以指定63/6 = 10不同的字母索引。让我们把这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. 源

掩蔽改进是相当好的,没有太多我们可以改进它。我们可以,但不值得这么复杂。

现在让我们找点其他需要改进的地方。随机数的来源。

有一个crypto/rand包,它提供了一个Read(b []byte)函数,所以我们可以使用它通过一次调用获得我们需要的任意多的字节。这在性能方面没有帮助,因为crypto/rand实现了一个加密安全的伪随机数生成器,所以它要慢得多。

所以让我们坚持使用math/rand包。rand.Rand使用rand.Source作为随机位的源。rand.Source是一个接口,它指定了一个Int63() int64方法:恰好是我们在最新解决方案中需要和使用的唯一方法。

因此,我们并不真正需要rand.Rand(显式的或全局的,共享的rand包),rand.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)
}

还要注意,最后一个解决方案不需要你初始化(播种)math/rand包的全局Rand,因为它没有被使用(并且我们的rand.Source已正确初始化/播种)。

这里还有一件事需要注意:math/rand的包文档声明:

默认的Source对于多个gorout例程并发使用是安全的。

因此,默认源比rand.NewSource()可能获得的Source要慢,因为默认源必须提供并发访问/使用下的安全性,而rand.NewSource()不提供这一点(因此它返回的Source更可能更快)。

7. 利用strings.Builder

所有之前的解决方案都返回一个string,其内容首先构建在一个片中(《创世纪》中的[]rune,后续解决方案中的[]byte),然后转换为string。最后的转换必须复制片的内容,因为string值是不可变的,如果转换不会复制,就不能保证字符串的内容不会通过其原始片被修改。详细信息请参见如何将utf8字符串转换为[]字节?Golang: []byte(string) vs []byte(*string)

Go 1.10引入了strings.Builder strings.Builder是一个新类型,我们可以使用它来构建类似于bytes.Bufferstring的内容。在内部,它使用[]byte来构建内容,当我们完成时,我们可以使用它的strings.Builder0方法获得最终的string值。但是它最酷的地方在于它不执行我们上面讨论的复制。它敢这样做是因为用于构建字符串内容的字节切片没有被暴露,因此可以保证没有人可以无意或恶意地修改它来改变产生的“不可变”。字符串。

因此,我们的下一个想法是不在切片中构建随机字符串,而是在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()
}

请注意,在创建一个新的strings.Buidler之后,我们调用了它的Builder.Grow()方法,确保它分配了一个足够大的内部片(以避免在添加随机字母时重新分配)。

8. “Mimicing"strings.Builder和包unsafe

strings.Builder在内部[]byte中构建字符串,与我们自己所做的一样。所以基本上通过strings.Builder来做会有一些开销,我们切换到strings.Builder的唯一原因是为了避免片的最终复制。

strings.Builder通过使用包unsafe避免最终的复制:

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

问题是,我们自己也可以这样做。因此,这里的想法是切换回在[]byte中构建随机字符串,但当我们完成后,不要将其转换为string来返回,而是进行不安全的转换:获得一个string,它指向我们的字节片作为字符串数据。

以下是具体的做法:

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。使用rand.Read())

Go 1.7新增是一个rand.Read()函数和Rand.Read()方法。为了获得更好的性能,我们应该尝试在一步中读取尽可能多的字节。

有一个小问题;我们需要多少字节?我们可以说:和输出字母的数量一样多。我们会认为这是一个较高的估计,因为一个字母索引使用不到8位(1字节)。但在这一点上,我们已经做得更糟了(因为得到随机的部分是“困难的部分”),我们得到的比需要的多。

还要注意,为了保持所有字母索引的平均分布,可能会有一些“垃圾”;我们无法使用的随机数据,因此我们最终会跳过一些数据,因此当我们通过所有字节切片时,就会结束。我们需要进一步“递归”地获取更多的随机字节。现在我们甚至失去了“对rand包的单个调用”。优势……

我们可以“多少”;优化我们从math.Rand()中获得的随机数据的使用。我们可以估计我们需要多少字节(位)。1个字母需要letterIdxBits位,我们需要n字母,所以我们需要n * letterIdxBits / 8.0字节四舍五入。我们可以计算一个随机索引不可用的概率(见上文),因此我们可以请求更多将“更有可能”;足够了(如果结果不是,我们就重复这个过程)。我们可以将字节片处理为“位流”。例如,我们有一个很好的第三方lib: github.com/icza/bitio(披露:我是作者)。

但基准测试代码仍然表明我们没有赢。为什么会这样?

最后一个问题的答案是,因为rand.Read()使用循环并不断调用Source.Int63(),直到它填充了传递的片。与RandStringBytesMaskImprSrc()解决方案所做的完全相同,没有是中间缓冲区,并且没有增加复杂性。这就是为什么RandStringBytesMaskImprSrc()仍然在王座上。是的,与rand.Read()不同,RandStringBytesMaskImprSrc()使用了未同步的rand.Source。但这种推理仍然适用;如果我们使用Rand.Read()而不是rand.Read()(前者也是不同步的),则可以证明这一点。

2基准

好了,现在是时候对不同的解决方案进行基准测试了。

关键时刻:

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

通过从符文转换到字节,我们立即获得了24%性能增益,内存需求下降到三分之一

去掉rand.Intn()并使用rand.Int63()来代替它,会得到另一个20%提升。

屏蔽(在大索引的情况下重复)会稍微变慢(由于重复调用):-22%

但当我们使用所有(或大部分)的63个随机位(10个索引来自一次rand.Int63()调用):这将大大加快:3倍. 0。

如果我们使用(非默认的,新的)rand.Source来代替rand.Rand,我们再次获得21%。

如果我们使用strings.Builder,我们在速度中获得了一个微小的3.5%,但我们也实现了内存使用和分配的50%减少!那很好啊!

最后,如果我们敢于使用包unsafe而不是strings.Builder,我们将再次获得一个漂亮的14%

将最终解决方案与初始解决方案进行比较:RandStringBytesMaskImprSrcUnsafe()6.3倍快而不是RandStringRunes(),使用六分之一内存和分配减少一半。任务完成

使用包uniuri,它生成加密安全的统一(无偏)字符串。

免责声明:我是该软件包的作者

icza's精彩解释的解决方案之后,这里是它的一个修改,使用crypto/rand而不是math/rand

const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)


func SecureRandomAlphaString(length int) string {


result := make([]byte, length)
bufferSize := int(float64(length)*1.3)
for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
if j%bufferSize == 0 {
randomBytes = SecureRandomBytes(bufferSize)
}
if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
result[i] = letterBytes[idx]
i++
}
}


return string(result)
}


// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
var randomBytes = make([]byte, length)
_, err := rand.Read(randomBytes)
if err != nil {
log.Fatal("Unable to generate random bytes")
}
return randomBytes
}

如果你想要一个更通用的解决方案,它允许你传入字符字节的切片来创建字符串,你可以尝试使用这个:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {


// Compute bitMask
availableCharLength := len(availableCharBytes)
if availableCharLength == 0 || availableCharLength > 256 {
panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
}
var bitLength byte
var bitMask byte
for bits := availableCharLength - 1; bits != 0; {
bits = bits >> 1
bitLength++
}
bitMask = 1<<bitLength - 1


// Compute bufferSize
bufferSize := length + length / 3


// Create random string
result := make([]byte, length)
for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
if j%bufferSize == 0 {
// Random byte buffer is empty, get a new one
randomBytes = SecureRandomBytes(bufferSize)
}
// Mask bytes to get an index into the character slice
if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
result[i] = availableCharBytes[idx]
i++
}
}


return string(result)
}

如果你想传入你自己的随机源,修改上面的内容以接受io.Reader而不是使用crypto/rand是很简单的。

如果您愿意向允许的字符池中添加一些字符,您可以使代码与任何通过io.Reader提供随机字节的东西一起工作。这里我们使用crypto/rand

// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"


// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
output := make([]byte, n)


// We will take n bytes, one byte for each character of output.
randomness := make([]byte, n)


// read all random
_, err := rand.Read(randomness)
if err != nil {
panic(err)
}


// fill output
for pos := range output {
// get random item
random := uint8(randomness[pos])


// random % 64
randomPos := random % uint8(len(encodeURL))


// put into output
output[pos] = encodeURL[randomPos]
}


return output
}

这是我的方式)使用数学兰特或加密兰特如你所愿。

func randStr(len int) string {
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)
// Base 64 can be longer than len
return str[:len]
}
const (
chars       = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
charsLen    = len(chars)
mask        = 1<<6 - 1
)


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


// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
/* chars 38个字符
* rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
*/
buf := make([]byte, ln)
for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
if remain == 0 {
cache, remain = rng.Int63(), 10
}
buf[idx] = chars[int(cache&mask)%charsLen]
cache >>= 6
remain--
idx--
}
return *(*string)(unsafe.Pointer(&buf))
}

BenchmarkRandStr16-8 20000000 68.1 ns/op 16 B/op 1分配/op

如果你想要密码安全随机数,并且确切的字符集是灵活的(比如base64就可以),你可以从期望的输出大小精确计算出你需要的随机字符的长度。

64进制文本比256进制文本长1/3。(2^8 vs 2^6;8位/6位= 1.333比率)

import (
"crypto/rand"
"encoding/base64"
"math"
)


func randomBase64String(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
}

注意:你也可以使用RawStdEncoding,如果你喜欢+和/字符-和_

如果你想要十六进制,以16为底比以256为底长2倍。(2^8 vs 2^4;8位/4位= 2x比例)

import (
"crypto/rand"
"encoding/hex"
"math"
)




func randomBase16String(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
}

但是,如果您的字符集使用base256到baseN编码器,则可以将其扩展到任何任意字符集。你可以用同样的大小计算需要多少位来表示你的字符集。任意字符集的比率计算是:ratio = 8 / log2(len(charset)))。

虽然这两种解决方案都是安全的,简单的,应该是快速的,并且不会浪费您的加密熵池。

这是一个游乐场,它适用于任何尺寸。https://play.golang.org/p/_yF_xxXer0Z

func Rand(n int) (str string) {
b := make([]byte, n)
rand.Read(b)
str = fmt.Sprintf("%x", b)
return
}
/*
korzhao
*/


package rand


import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)


// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
Seed int64
Pool *sync.Pool
}


var (
MRand    = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)


// init random number generator
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}


// get the seed
func getSeed() int64 {
return time.Now().UnixNano()
}


func (s *Rand) getrand() *rand.Rand {
return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
s.Pool.Put(r)
}


// get a random number
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)


return r.Intn(n)
}


//  bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
r := s.getrand()
defer s.putrand(r)


return r.Read(p)
}


func CreateRandomString(len int) string {
b := make([]byte, len)
_, err := MRand.Read(b)
if err != nil {
return ""
}
for i := 0; i < len; i++ {
b[i] = randlist[b[i]%(62)]
}
return *(*string)(unsafe.Pointer(&b))
}

24.0 ns/op 16 B/op 1 allocs/

这里是一个简单而高效的加密安全随机字符串的解决方案。

package main


import (
"crypto/rand"
"unsafe"
"fmt"
)


var alphabet = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")


func main() {
fmt.Println(generate(16))
}


func generate(size int) string {
b := make([]byte, size)
rand.Read(b)
for i := 0; i < size; i++ {
b[i] = alphabet[b[i] % byte(len(alphabet))]
}
return *(*string)(unsafe.Pointer(&b))
}

基准

Benchmark  95.2 ns/op      16 B/op      1 allocs/op

简单的解决方案,最少重复的结果:

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


func randomString(length int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, length)
rand.Read(b)
return fmt.Sprintf("%x", b)[:length]
}

操场上中检查它

另一个版本,灵感来自生成密码在JavaScript加密:

package main


import (
"crypto/rand"
"fmt"
)


var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"


func shortID(length int) string {
ll := len(chars)
b := make([]byte, length)
rand.Read(b) // generates len(b) random bytes
for i := 0; i < length; i++ {
b[i] = chars[int(b[i])%ll]
}
return string(b)
}


func main() {
fmt.Println(shortID(18))
fmt.Println(shortID(18))
fmt.Println(shortID(18))
}

这是一个示例代码,我用来生成证书号码在我的应用程序。

func GenerateCertificateNumber() string {
CertificateLength := 7
t := time.Now().String()
CertificateHash, err := bcrypt.GenerateFromPassword([]byte(t), bcrypt.DefaultCost)
if err != nil {
fmt.Println(err)
}
// Make a Regex we only want letters and numbers
reg, err := regexp.Compile("[^a-zA-Z0-9]+")
if err != nil {
log.Fatal(err)
}
processedString := reg.ReplaceAllString(string(CertificateHash), "")
fmt.Println(string(processedString))


CertificateNumber := strings.ToUpper(string(processedString[len(processedString)-CertificateLength:]))
fmt.Println(CertificateNumber)
return CertificateNumber
}

作为icza's绝妙解决方案的后续,下面我将使用rand.Reader

func RandStringBytesMaskImprRandReaderUnsafe(length uint) (string, error) {
const (
charset     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
charIdxBits = 6                  // 6 bits to represent a letter index
charIdxMask = 1<<charIdxBits - 1 // All 1-bits, as many as charIdxBits
charIdxMax  = 63 / charIdxBits   // # of letter indices fitting in 63 bits
)


buffer := make([]byte, length)
charsetLength := len(charset)
max := big.NewInt(int64(1 << uint64(charsetLength)))


limit, err := rand.Int(rand.Reader, max)
if err != nil {
return "", err
}


for index, cache, remain := int(length-1), limit.Int64(), charIdxMax; index >= 0; {
if remain == 0 {
limit, err = rand.Int(rand.Reader, max)
if err != nil {
return "", err
}


cache, remain = limit.Int64(), charIdxMax
}


if idx := int(cache & charIdxMask); idx < charsetLength {
buffer[index] = charset[idx]
index--
}


cache >>= charIdxBits
remain--
}


return *(*string)(unsafe.Pointer(&buffer)), nil
}




func BenchmarkBytesMaskImprRandReaderUnsafe(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()


const length = 16


b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
RandStringBytesMaskImprRandReaderUnsafe(length)
}
})
}

如果需要选择是否大写,我通常会这样做

func randomString(length int, upperCase bool) string {
rand.Seed(time.Now().UnixNano())


var alphabet string


if upperCase {
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
} else {
alphabet = "abcdefghijklmnopqrstuvwxyz"
}


var sb strings.Builder


l := len(alphabet)


for i := 0; i < length; i++ {
c := alphabet[rand.Intn(l)]
sb.WriteByte(c)
}


return sb.String()
}

如果不需要大写字母,就像这样

func randomString(length int) string {
rand.Seed(time.Now().UnixNano())


var alphabet string = "abcdefghijklmnopqrstuvwxyz"
var sb strings.Builder


l := len(alphabet)


for i := 0; i < length; i++ {
c := alphabet[rand.Intn(l)]
sb.WriteByte(c)
}


return sb.String()
}
package main


import (
"encoding/base64"
"fmt"
"math/rand"
"time"
)


// customEncodeURL is like `bas64.encodeURL`
// except its made up entirely of uppercase characters:
const customEncodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKL"


// Random generates a random string.
// It is not cryptographically secure.
func Random(n int) string {
b := make([]byte, n)
rand.Seed(time.Now().UnixNano())
_, _ = rand.Read(b) // docs say that it always returns a nil error.


customEncoding := base64.NewEncoding(customEncodeURL).WithPadding(base64.NoPadding)
return customEncoding.EncodeToString(b)
}


func main() {
fmt.Println(Random(16))
}