string 如何在 Go 中生成固定长度的随机字符串?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22892120/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 02:15:24  来源:igfitidea点击:

How to generate a random string of a fixed length in Go?

stringrandomgo

提问by Anish Shah

I want a random string of characters only (uppercase or lowercase), no numbers, in Go. What is the fastest and simplest way to do this?

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

回答by icza

Paul's solutionprovides a simple, general solution.

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

The question asks for the "the fastest and simplest way". Let's address the fastestpart too. We'll arrive at our final, fastest code in an iterative manner. Benchmarking each iteration can be found at the end of the answer.

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

All the solutions and the benchmarking code can be found on the Go Playground. The code on the Playground is a test file, not an executable. You have to save it into a file named XX_test.goand run it with

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

go test -bench . -benchmem

Foreword:

前言

The fastest solution is not a go-to solution if you just need a random string. For that, Paul's solution is perfect. This is if performance does matter. Although the first 2 steps (Bytesand Remainder) might be an acceptable compromise: they do improve performance by like 50% (see exact numbers in the II. Benchmarksection), and they don't increase complexity significantly.

如果您只需要一个随机字符串,最快的解决方案不是首选解决方案。为此,保罗的解决方案是完美的。这就是性能是否重要。尽管前两个步骤(BytesRemainder)可能是一个可以接受的折衷方案:它们确实将性能提高了 50%(参见II. Benchmark部分中的确切数字),并且它们不会显着增加复杂性。

Having said that, even if you don't need the fastest solution, reading through this answer might be adventurous and educational.

话虽如此,即使您不需要最快的解决方案,通读此答案也可能具有冒险精神和教育意义。

I. Improvements

一、改进

1. Genesis (Runes)

1. 创世纪(符文)

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

2. 字节

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).

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

So instead of:

所以而不是:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

we can use:

我们可以用:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Or even better:

或者甚至更好:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

Now this is already a big improvement: we could achieve it to be a const(there are stringconstants but there are no slice constants). As an extra gain, the expression len(letters)will also be a const! (The expression len(s)is constant if sis a string constant.)

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

And at what cost? Nothing at all. strings can be indexed which indexes its bytes, perfect, exactly what we want.

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

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

3. 余数

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().

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

This is much slower compared to rand.Int63()which produces a random number with 63 random bits.

rand.Int63()生成具有 63 个随机位的随机数相比,这要慢得多。

So we could simply call rand.Int63()and use the remainder after dividing by len(letterBytes):

所以我们可以简单地调用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)
}

This works and is significantly faster, the disadvantage is that the probability of all the letters will not be exactly the same (assuming rand.Int63()produces all 63-bit numbers with equal probability). Although the distortion is extremely small as the number of letters 52is much-much smaller than 1<<63 - 1, so in practice this is perfectly fine.

这有效并且明显更快,缺点是所有字母的概率不会完全相同(假设rand.Int63()产生所有 63 位数字的概率相等)。尽管由于字母数量52远小于1<<63 - 1,因此失真非常小,因此在实践中这完全没问题。

To make this understand easier: let's say you want a random number in the range of 0..5. Using 3 random bits, this would produce the numbers 0..1with double probability than from the range 2..5. Using 5 random bits, numbers in range 0..1would occur with 6/32probability and numbers in range 2..5with 5/32probability which is now closer to the desired. Increasing the number of bits makes this less significant, when reaching 63 bits, it is negligible.

为了使这更容易理解:假设您想要一个范围为 的随机数0..5。使用 3 个随机位,这将产生0..1比范围 2 倍概率的数字2..5。使用5个随机比特,在数字范围0..1将与发生6/32概率和数字范围2..55/32概率现在是更接近期望。增加位数会使这一点变得不那么重要,当达到 63 位时,可以忽略不计。

4. Masking

4. 掩蔽

Building on the previous solution, we can maintain the equal distribution of letters by using only as many of the lowest bits of the random number as many is required to represent the number of letters. So for example if we have 52 letters, it requires 6 bits to represent it: 52 = 110100b. So we will only use the lowest 6 bits of the number returned by rand.Int63(). And to maintain equal distribution of letters, we only "accept" the number if it falls in the range 0..len(letterBytes)-1. If the lowest bits are greater, we discard it and query a new random number.

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

Note that the chance of the lowest bits to be greater than or equal to len(letterBytes)is less than 0.5in general (0.25on average), which means that even if this would be the case, repeating this "rare" case decreases the chance of not finding a good number. After nrepetition, the chance that we sill don't have a good index is much less than pow(0.5, n), and this is just an upper estimation. In case of 52 letters the chance that the 6 lowest bits are not good is only (64-52)/64 = 0.19; which means for example that chances to not have a good number after 10 repetition is 1e-8.

请注意,最低位大于或等于的机会len(letterBytes)通常小于0.50.25平均),这意味着即使是这种情况,重复这种“罕见”的情况也会降低找不到好的机会数字。n重复之后,我们仍然没有一个好的指数的机会远小于pow(0.5, n),这只是一个上限估计。在 52 个字母的情况下,只有 6 个最低位不好的机会(64-52)/64 = 0.19;这意味着例如,在 10 次重复后没有好数字的机会是1e-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

5.掩蔽改进

The previous solution only uses the lowest 6 bits of the 63 random bits returned by rand.Int63(). This is a waste as getting the random bits is the slowest part of our algorithm.

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

If we have 52 letters, that means 6 bits code a letter index. So 63 random bits can designate 63/6 = 10different letter indices. Let's use all those 10:

如果我们有 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. Source

6. 来源

The Masking Improvedis 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.

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

There is a crypto/randpackage which provides a Read(b []byte)function, so we could use that to get as many bytes with a single call as many we need. This wouldn't help in terms of performance as crypto/randimplements a cryptographically secure pseudorandom number generator so it's much slower.

有一个crypto/rand包提供了一个Read(b []byte)函数,因此我们可以使用它通过一次调用获取尽可能多的字节。这在性能方面无济于事,因为crypto/rand实现了加密安全的伪随机数生成器,因此速度要慢得多。

So let's stick to the math/randpackage. The rand.Randuses a rand.Sourceas the source of random bits. rand.Sourceis an interface which specifies a Int63() int64method: exactly and the only thing we needed and used in our latest solution.

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

So we don't really need a rand.Rand(either explicit or the global, shared one of the randpackage), a rand.Sourceis perfectly enough for us:

所以我们真的不需要一个rand.Rand(显式的或全局的,共享的rand包),arand.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)
}

Also note that this last solution doesn't require you to initialize (seed) the global Randof the math/randpackage as that is not used (and our rand.Sourceis properly initialized / seeded).

还要注意的是这最后的解决方案不要求你进行初始化(种子)在全球Rand的的math/rand包中未使用(和我们的rand.Source正确初始化/种子)。

One more thing to note here: package doc of math/randstates:

这里还要注意一件事:math/rand状态包文档:

The default Source is safe for concurrent use by multiple goroutines.

默认的 Source 可以安全地被多个 goroutines 并发使用。

So the default source is slower than a Sourcethat may be obtained by rand.NewSource(), because the default source has to provide safety under concurrent access / use, while rand.NewSource()does not offer this (and thus the Sourcereturned by it is more likely to be faster).

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

7. Utilizing strings.Builder

7.利用 strings.Builder

All previous solutions return a stringwhose content is first built in a slice ([]runein Genesis, and []bytein subsequent solutions), and then converted to string. This final conversion has to make a copy of the slice's content, because stringvalues are immutable, and if the conversion would not make a copy, it could not be guaranteed that the string's content is not modified via its original slice. For details, see How to convert utf8 string to []byte?and golang: []byte(string) vs []byte(*string).

之前的所有解决方案都返回 a ,string其内容首先构建在切片中([]runeGenesis[]byte后续解决方案中),然后转换为string. 这个最终的转换必须复制切片的内容,因为string值是不可变的,如果转换不会复制,则不能保证字符串的内容不会通过其原始切片进行修改。有关详细信息,请参阅如何将 utf8 字符串转换为 []byte?golang: []byte(string) vs []byte(*string)

Go 1.10 introduced strings.Builder.strings.Buildera new type we can use to build contents of a stringsimilar to bytes.Buffer. It does it internally using a []byte, and when we're done, we can obtain the final stringvalue using its Builder.String()method. But what's cool in it is that it does this without performing the copy we just talked about above. It dares to do so because the byte slice used to build the string's content is not exposed, so it is guaranteed that no one can modify it unintentionally or maliciously to alter the produced "immutable" string.

Go 1.10 引入strings.Builderstrings.Builder我们可以使用一种新类型来构建string类似于bytes.Buffer. 它在内部使用 a[]byte来完成,当我们完成时,我们可以string使用它的Builder.String()方法获得最终值。但它很酷的地方在于,它无需执行我们刚才谈到的复制即可完成此操作。它之所以敢这样做,是因为用于构建字符串内容的字节片没有暴露,所以保证没有人可以无意或恶意修改它来改变产生的“不可变”字符串。

So our next idea is to not build the random string in a slice, but with the help of a strings.Builder, so once we're done, we can obtain and return the result without having to make a copy of it. This may help in terms of speed, and it will definitely help in terms of memory usage and allocations.

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

Do note that after creating a new strings.Buidler, we called its Builder.Grow()method, making sure it allocates a big-enough internal slice (to avoid reallocations as we add the random letters).

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

8. "Mimicing" strings.Builderwith package unsafe

8.strings.Builder用包“模仿”unsafe

strings.Builderbuilds the string in an internal []byte, the same as we did ourselves. So basically doing it via a strings.Builderhas some overhead, the only thing we switched to strings.Builderfor is to avoid the final copying of the slice.

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

strings.Builderavoids the final copy by using package unsafe:

strings.Builder通过使用 package 避免最终副本unsafe

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

The thing is, we can also do this ourselves, too. So the idea here is to switch back to building the random string in a []byte, but when we're done, don't convert it to stringto return, but do an unsafe conversion: obtain a stringwhich points to our byte slice as the string data.

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

This is how it can be done:

这是如何做到的:

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())

(9. 使用rand.Read())

Go 1.7 addeda 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.

Go 1.7 添加了一个rand.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.

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

Also note that to maintain equal distribution of all letter indices, there might be some "garbage" random data that we won't be able to use, so we would end up skipping some data, and thus end up short when we go through all the byte slice. We would need to further get more random bytes, "recursively". And now we're even losing the "single call to randpackage" advantage...

另请注意,为了保持所有字母索引的均匀分布,可能会有一些我们无法使用的“垃圾”随机数据,因此我们最终会跳过一些数据,因此在我们遍历所有数据时最终会很短字节切片。我们需要进一步“递归地”获得更多随机字节。现在我们甚至失去了“单一呼叫rand打包”的优势......

We could "somewhat" optimize the usage of the random data we acquire from math.Rand(). We may estimate how many bytes (bits) we'll need. 1 letter requires letterIdxBitsbits, and we need nletters, so we need n * letterIdxBits / 8.0bytes rounding up. We can calculate the probability of a random index not being usable (see above), so we could request more that will "more likely" be enough (if it turns out it's not, we repeat the process). We can process the byte slice as a "bit stream" for example, for which we have a nice 3rd party lib: github.com/icza/bitio(disclosure: I'm the author).

我们可以“稍微”优化我们从中获取的随机数据的使用math.Rand()。我们可以估计需要多少字节(位)。1 个字母需要letterIdxBits位,而我们需要n字母,因此我们需要对n * letterIdxBits / 8.0字节进行四舍五入。我们可以计算一个随机索引不可用的概率(见上文),所以我们可以请求更多“更有可能”就足够了(如果事实证明不是,我们重复这个过程)。例如,我们可以将字节切片作为“位流”进行处理,为此我们有一个不错的 3rd 方库:(github.com/icza/bitio披露:我是作者)。

But Benchmark code still shows we're not winning. Why is it so?

但是基准代码仍然显示我们没有获胜。为什么会这样?

The answer to the last question is because rand.Read()uses a loop and keeps calling Source.Int63()until it fills the passed slice. Exactly what the RandStringBytesMaskImprSrc()solution does, withoutthe intermediate buffer, and without the added complexity. That's why RandStringBytesMaskImprSrc()remains on the throne. Yes, RandStringBytesMaskImprSrc()uses an unsynchronized rand.Sourceunlike rand.Read(). But the reasoning still applies; and which is proven if we use Rand.Read()instead of rand.Read()(the former is also unsynchronzed).

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

II. Benchmark

二、基准

All right, it's time for benchmarking the different solutions.

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

Moment of truth:

关键时刻:

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

Just by switching from runes to bytes, we immediately have 24%performance gain, and memory requirement drops to one third.

仅仅通过从符文切换到字节,我们立即获得了24% 的性能提升,内存需求下降到三分之一

Getting rid of rand.Intn()and using rand.Int63()instead gives another 20%boost.

摆脱rand.Intn()并使用rand.Int63()它会带来另外20% 的提升。

Masking (and repeating in case of big indices) slows down a little (due to repetition calls): -22%...

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

But when we make use of all (or most) of the 63 random bits (10 indices from one rand.Int63()call): that speeds up big time: 3 times.

但是,当我们使用全部(或大部分)63 个随机位(来自一次rand.Int63()调用的10 个索引)时:这加快了时间:3 倍

If we settle with a (non-default, new) rand.Sourceinstead of rand.Rand, we again gain 21%.

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

If we utilize strings.Builder, we gain a tiny 3.5%in speed, but we also achieved 50%reduction in memory usage and allocations! That's nice!

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

Finally if we dare to use package unsafeinstead of strings.Builder, we again gain a nice 14%.

最后,如果我们敢于使用 packageunsafe而不是strings.Builder,我们再次获得了不错的14%

Comparing the final to the initial solution: RandStringBytesMaskImprSrcUnsafe()is 6.3 times fasterthan RandStringRunes(), uses one sixthmemory and half as few allocations. Mission accomplished.

最后进行比较来对初始解:RandStringBytesMaskImprSrcUnsafe()快6.3倍RandStringRunes(),使用六分之一存储器和半尽可能少的分配。任务完成。

回答by Paul Hankin

You can just write code for it. This code can be a little simpler if you want to rely on the letters all being single bytes when encoded in UTF-8.

你可以只为它编写代码。如果您希望在以 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))
}

回答by dchest

Use package uniuri, which generates cryptographically secure uniform (unbiased) strings.

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

Disclaimer: I'm the author of the package

免责声明:我是包的作者

回答by Not_a_Golfer

Two possible options (there might be more of course):

两种可能的选择(当然可能还有更多):

  1. You can use the crypto/randpackage that supports reading random byte arrays (from /dev/urandom) and is geared towards cryptographic random generation. see http://golang.org/pkg/crypto/rand/#example_Read. It might be slower than normal pseudo-random number generation though.

  2. Take a random number and hash it using md5 or something like this.

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

  2. 取一个随机数并使用 md5 或类似的东西对其进行散列。

回答by Chris

Following icza'swonderfully explained solution, here is a modification of it that uses crypto/randinstead of math/rand.

遵循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
}

If you want a more generic solution, that allows you to pass in the slice of character bytes to create the string out of, you can try using this:

如果您想要一个更通用的解决方案,允许您传入字符字节片段以从中创建字符串,您可以尝试使用以下方法:

// 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)
}

If you want to pass in your own source of randomness, it would be trivial to modify the above to accept an io.Readerinstead of using crypto/rand.

如果你想传入你自己的随机源,修改上面的代码以接受 anio.Reader而不是使用crypto/rand.

回答by Steven Soroka

If you want cryptographically securerandom numbers, and the exact charset is flexible (say, base64 is fine), you can calculate exactly what the length of random characters you need from the desired output size.

如果您想要加密安全的随机数,并且确切的字符集是灵活的(例如,base64 很好),您可以根据所需的输出大小准确计算出您需要的随机字符的长度。

Base 64 text is 1/3 longer than base 256. (2^8 vs 2^6; 8bits/6bits = 1.333 ratio)

Base 64 文本比 base 256 长 1/3。(2^8 vs 2^6;8bits/6bits = 1.333 比率)

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

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Round(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
}

Note: you can also use RawStdEncoding if you prefer + and / characters to - and _

注意:如果您更喜欢 + 和 / 字符而不是 - 和 _,您也可以使用 RawStdEncoding

If you want hex, base 16 is 2x longer than base 256. (2^8 vs 2^4; 8bits/4bits = 2x ratio)

如果你想要十六进制,基数 16 比基数 256 长 2 倍。(2^8 vs 2^4;8bits/4bits = 2x 比率)

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


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Round(float64(l)/2)))
    rand.Read(buff)
    str := hex.EncodeToString(buff)
    return str[:l] // strip 1 extra character we get from odd length results
}

However, you could extend this to any arbitrary character set if you have a base256 to baseN encoder for your character set. You can do the same size calculation with how many bits are needed to represent your character set. The ratio calculation for any arbitrary charset is: ratio = 8 / log2(len(charset))).

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

Though both of these solutions are secure, simple, should be fast, and don't waste your crypto entropy pool.

尽管这两种解决方案都安全、简单、应该很快,并且不会浪费您的加密熵池。

Here's the playground showing it works for any size. https://play.golang.org/p/i61WUVR8_3Z

这是显示它适用于任何尺寸的操场。https://play.golang.org/p/i61WUVR8_3Z

回答by kevin

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

回答by Dima

Here is my way ) Use math rand or crypto rand as you wish.

这是我的方式)根据需要使用数学兰特或加密兰特。

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]
}

回答by 0xcaff

If you are willing to add a few characters to your pool of allowed characters, you can make the code work with anything which provides random bytes through a io.Reader. Here we are using 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
}

回答by user10987909

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 allocs/op

BenchmarkRandStr16-8 20000000 68.1 ns/op 16 B/op 1 allocs/op