当前位置: 首页 > 知识库问答 >
问题:

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

薛宜
2023-03-14

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

共有3个答案

冯阳成
2023-03-14

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

声明:我是这个包的作者

欧照
2023-03-14

您可以为它编写代码。如果您想在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))
}
淳于熙云
2023-03-14

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

这个问题要求“最快、最简单的方法”。让我们也讨论一下最快的部分。我们将以迭代的方式得到最终的、最快的代码。每个迭代的基准可以在答案的末尾找到。

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

go test -bench . -benchmem

前言:

如果您只需要一个随机字符串,那么最快的解决方案不是“去解决方案”。为此,保罗的解决方案是完美的。这就是性能是否重要。尽管前两个步骤(字节和余数)可能是一个可以接受的折衷方案:它们确实提高了50%的性能(请参阅II.基准部分中的确切数字),并且不会显著增加复杂性。

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

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

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

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

因此,而不是:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

我们可以使用:

var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

或者更好:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

现在这已经是一个很大的改进:我们可以将其实现为<code>常量(有<code>字符串len(字母)也将是常量!(如果<code>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)
}

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

与<code>兰德相比,这要慢得多。Int63()生成一个包含63个随机位的随机数。

所以我们可以简单地调用< code>rand。Int63()并使用除以< code>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)
}

这是可行的,而且速度明显更快,缺点是所有字母的概率不会完全相同(假设< code>rand。Int63()以相等的概率产生所有63位数字)。尽管失真非常小,因为字母< code>52的数量比< code>1小得多

为了便于理解:假设您想要一个 0..5 范围内的随机数。使用3个随机位,这将产生数字0..1,概率比范围2..5的概率高一倍。使用5个随机位,范围为0..1的数字将以6/32的概率出现,范围为2..5的数字将以5/32的概率出现,现在更接近所需的概率。增加位数会使这一点变得不那么重要,当达到63位时,它可以忽略不计。

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

请注意,最低位大于或等于len(letterBytes)的机会一般小于0.5(平均0.25),这意味着即使是这样,重复这种“罕见”的情况也会降低找不到好数字的机会。在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)
}

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

如果我们有52个字母,这意味着6位编码一个字母索引。因此63个随机位可以指定< code>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)
}

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

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

有一个<code>crypto/rand函数,因此我们可以使用该函数通过一次调用获得所需的字节数。这对性能没有帮助,因为<code>crypto/rand<code>实现了一个加密安全的伪随机数生成器,因此速度要慢得多。

因此,让我们坚持数学/兰特套餐。兰特。兰德使用兰特。源作为随机位的源。兰特。Source 是一个接口,它指定了 Int63() int64 方法:正是我们在最新解决方案中需要和使用的唯一方法。

所以我们真的不需要兰特。兰特(无论是显式的还是全局的,共享的兰特包之一),兰特。来源对我们来说已经足够了:

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

还要注意,最后一个解决方案不需要您初始化(播种)< code>math/rand包的全局< code>Rand,因为它没有被使用(以及我们的< code>Rand。源已正确初始化/植入)。

这里还有一件事要注意:数学/兰德的包文档指出:

默认源对于多个goroutine并发使用是安全的。

因此,默认源比<code>rand获得的<code>源,因为默认源必须在并发访问/使用时提供安全性,而<code>是随机的。NewSource()不提供此功能(因此,它返回的<code>源代码

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

Go 1.10引入了<code>strings.Builder<代码>字符串。生成器是一种新类型,我们可以使用它来构建<code>字符串。在内部,它使用一个<code>〔〕字节方法。但最酷的是,它没有执行我们刚才讨论的复制。它敢于这样做,因为用于构建字符串内容的字节片没有公开,因此可以保证没有人可以无意或恶意地修改它以更改生成的“不可变”字符串。

因此,我们的下一个想法不是在切片中构建随机字符串,而是在<code>字符串的帮助下。生成器,因此一旦完成,我们就可以获得并返回结果,而无需复制它。这在速度方面可能会有所帮助,在内存使用和分配方面肯定会有所帮助。

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

请注意,在创建一个新的< code >字符串后。Buidler,我们称其为< code>Builder。Grow()方法,确保它分配了一个足够大的内部片(以避免我们添加随机字母时的重新分配)。

<code>字符串。生成器将字符串构建在一个内部的[]字节中,与我们自己所做的相同。所以基本上是通过<code>字符串实现的。生成器有一些开销,这是我们唯一切换到<code>字符串的东西。生成器用于避免切片的最终复制。

字符串。Builder 通过使用不安全的包来避免最终副本:

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

问题是,我们自己也可以这样做。因此,这里的想法是切换回在<code>〔〕字节

这是如何做到的:

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

Go 1.7增加了一个< code>rand。Read()函数和一个< code>Rand。Read()方法。为了获得更好的性能,我们应该尝试使用这些方法在一个步骤中读取尽可能多的字节。

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

还要注意,为了保持所有字母索引的平均分布,可能会有一些我们无法使用的“垃圾”随机数据,因此我们最终会跳过一些数据,从而在遍历所有字节片时结束。我们需要进一步“递归地”获取更多的随机字节。现在,我们甚至失去了“一次性调用< code>rand包”的优势...

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

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

最后一个问题的答案是因为<code>兰德。Read()使用循环并不断调用<code>源代码。Int63()直到它填满传递的切片。这正是<code>randstringbytesmaskimpsrc()不同于和rand.Read()。但推理仍然适用;如果我们使用<code>Rand,则证明了这一点。Read()而不是兰德。Read()(前者也不同步)。

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

真相时刻:

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%的性能提升,内存需求下降到三分之一。

摆脱<code>兰德。Intn()并使用<code>兰德。Int63()反而提供了另一个20%的提升。

屏蔽(以及在大指数的情况下重复)会减慢一点速度(由于重复调用):-22%...

但是,当我们使用全部(或大多数)63个随机位(来自一兰特的10个索引。Int63() 调用):加快了大时间:3 倍。

如果我们以(非默认,新的)rand结算。源代码,而不是兰德。兰德,我们再次获得21%。

如果我们使用< code >字符串。构建器,我们在速度上只获得了3.5%的微小提升,但是在内存使用和分配上也实现了50%的减少!真好!

最后,如果我们敢于使用包< code>unsafe而不是< code >字符串。建设者,我们再次获得了不错的14%。

将最终解决方案与初始解决方案进行比较:< code > randstringbytesmaskprsrcunsafe()比< code>RandStringRunes()快6.3倍,使用六分之一的内存和一半的分配。任务完成。

 类似资料:
  • 问题内容: 在Go语言中,我只需要一个随机的字符串(大写或小写),没有数字。最快和最简单的方法是什么? 问题答案: Paul的解决方案提供了一个 简单的 通用解决方案。 问题要求 “最快,最简单的方法” 。让我们也讨论 最快的 部分。我们将以迭代的方式得出最终的最快的代码。对每个迭代进行基准测试可以在答案的结尾处找到。 所有解决方案和基准代码都可以在GoPlayground上找到。Playgrou

  • 问题内容: 我有一个字节数组,固定长度为4。 我需要将每个字节设置为随机字节。如何以最有效的方式这样做?就我而言,这些方法没有提供随机字节功能。 也许有一种内置的方式,还是我应该生成一个随机字符串并将其转换为字节数组? 问题答案: 包兰特 func Read 读取从默认源生成len(p)个随机字节,并将它们写入p。它总是返回len(p)和nil错误。 f unc(* Rand)读 读取生成len(

  • 本文向大家介绍在JavaScript中生成指定长度的随机字符串,包括了在JavaScript中生成指定长度的随机字符串的使用技巧和注意事项,需要的朋友参考一下 我们需要编写一个JavaScript函数,该函数接受数字n,并返回长度为n的随机字符串,其中不包含26个英文小写字母。 因此,让我们为该函数编写代码- 示例 为此的代码将是- 输出结果 控制台中的输出将为- 注意-这是许多可能的输出之一。控

  • 本文向大家介绍Python生成给定长度的随机字符串,包括了Python生成给定长度的随机字符串的使用技巧和注意事项,需要的朋友参考一下 在本文中,我们将看到如何生成具有给定长度的随机字符串。这在创建需要随机性的随机密码或其他程序时很有用。 random.choices 随机模块中的choices函数可以产生字符串,然后可以将其连接以创建给定长度的字符串。 示例 输出结果 运行上面的代码给我们以下结

  • 我想生成一个固定长度的散列字符串。我正在为此使用MessageDigest API。我注意到API中的这个函数,但它返回的是整数而不是字节数组。 当我尝试使用此重载摘要方法时,我得到 java.security.DigestException:对于 SHA-256 摘要,长度必须至少为 32,或者输出缓冲区对于指定的偏移量和长度来说太小。 有人能举个例子来说明如何生成固定长度的哈希值吗?

  • 问题内容: 我需要生成许多固定长度的随机十六进制字符串。 我正在做这样的事情: 但是我遇到了这个紧急错误 错误似乎在 ,但我不知道为什么会出现此错误。 在Go中生成大量固定长度的随机十六进制字符串的最快,最简单的方法是什么? 基准测试 => icza 解决方案更有效 问题答案: 实际上,您发布的代码可以运行,即使其中有错误(请参见下文),也不会引起恐慌(只会使性能变差)。 您发布的堆栈跟踪指示软件