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


300

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


2
@VinceEmigh:这是一个讨论基本问题的元主题。 meta.stackoverflow.com/q/274645/395461就我个人而言,我认为基本问题如果写得好并且是主题性的就可以。查看下面的答案,它们说明了很多对新人有用的东西。For循环,类型转换,使()等
香农·马修斯

2
@Shannon“ 此问题未显示任何研究成果 ”(您的链接中第一个被强烈推崇的答案)-这就是我所指的。他没有表现出研究努力。根本不做任何努力(尝试,甚至表明他看上去在线,但显然他没有)。虽然这将是有用的人新的,本网站不专注于教导新人。它专注于回答特定的编程问题/问题,而不是教程/指南。尽管可以将其用于后者,但这不是重点,因此应该解决这个问题。取而代之的是//
Vince Emigh

9
@VinceEmigh一年前我问了这个问题。我在网上搜索了随机字符串并阅读了文档。但这没有帮助。如果我没有写这个问题,那并不意味着我还没有研究。
阿尼沙(Anish Shah)

Answers:


808

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

问题要求“最快,最简单的方法”。让我们也讨论最快的部分。我们将以迭代的方式得出最终的最快的代码。对每个迭代进行基准测试可以在答案的结尾处找到。

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

go test -bench . -benchmem

前言

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

话虽如此,即使您不需要最快的解决方案,通读此答案也可能是冒险和有益的。

一,改进

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-to-1字节(是Go存储字符串的方式)。

所以代替:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

我们可以用:

var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

甚至更好:

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

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

而且要花多少钱?没事 string可以对s进行索引,从而对其字节进行索引,这正是我们想要的。

我们的下一个目的地如下所示:

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

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比range两倍的概率2..5。使用5个随机位,范围内的数字0..1将以6/32概率出现,范围内的数字2..5将以5/32概率出现,现在更接近所需值。增加位数会使此重要性降低,当达到63位时,可以忽略不计。

4.遮罩

在前面的解决方案的基础上,我们可以通过使用与代表字母数量所需的数量一样多的随机数最低位来维持字母的均等分布。因此,例如,如果我们有52个字母,则需要6位才能表示它:52 = 110100b。因此,我们将仅使用所返回数字的最低6位rand.Int63()。为了保持字母的均等分布,我们仅在数字落入范围内时才“接受”该数字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)
}

5.改进了遮罩

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

如果我们有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对于我们来说,a 足够了:

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

还要注意,最后一个解决方案不需要您初始化(种子)全局变量。 Randmath/rand未使用的程序包变量(并且rand.Source已正确初始化/植入了种子)。

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

默认的Source对于多个goroutine可以安全地同时使用。

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

7.利用 strings.Builder

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

Go 1.10推出strings.Builder strings.Builder一种新的类型,我们可以用它来建立string类似的内容bytes.Buffer。它在内部使用进行操作[]byte,完成后,我们可以string使用其Builder.String()方法获得最终值。但是,最酷的是它可以执行此操作而不执行我们上面刚刚谈到的复制操作。之所以敢于这样做,是因为未暴露用于构建字符串内容的字节片,因此可以确保没有人可以无意或恶意地修改它来更改生成的“不可变”字符串。

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

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

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

strings.Builder[]byte就像我们自己一样,在内部构建字符串。因此,基本上通过a进行操作会strings.Builder产生一些开销,我们切换到的唯一strings.Builder方法是避免切片的最终复制。

strings.Builder通过使用package避免最终副本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字节舍入。我们可以计算出随机索引不可用的可能性(请参见上文),因此我们可以请求更多的“可能”就足够了(如果事实并非如此,则重复此过程)。例如,我们可以将字节切片作为“位流”进行处理,为此,我们有一个不错的第三方库:github.com/icza/bitio公开:我是作者)。

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

最后一个问题的答案是因为rand.Read()使用循环并不断调用,Source.Int63()直到它填充了传递的切片为止。RandStringBytesMaskImprSrc()解决方案的确切功能是,没有中间缓冲区,也没有增加复杂性。这就是为什么RandStringBytesMaskImprSrc()继续保持王位。是,RandStringBytesMaskImprSrc()使用了不同步的rand.Source不同于rand.Read()。但是推理仍然适用。如果我们使用Rand.Read()代替rand.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%的性能提升,而内存需求则降至三分之一

摆脱rand.Intn()和使用rand.Int63()反而给了另一个 20%提高。

遮罩(如果索引较大,则重复)会稍微减慢(由于重复调用):- 22% ...

但是,当我们使用63个随机位的全部(或大部分)(一次rand.Int63()调用即可获得10个索引)时,这可以节省大量时间:3倍

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

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

最后,如果我们敢于使用package unsafe而不是strings.Builder,我们将再次获得14%的收益。

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


8
@RobbieV是的,因为使用了共享rand.Source。更好的解决方法是将a传递rand.SourceRandStringBytesMaskImprSrc()函数,这样就不需要锁定,因此不会影响性能/效率。每个goroutine可以有自己的Source
icza 2015年

113
@icza,这是我很长时间以来在SO上看到的最佳答案之一!
astropanic

1
@MikeAtlas:defer当明显不需要它时,应避免使用它。见grokbase.com/t/gg/golang-nuts/158zz5p42w/...
咱山猫

1
@ZanLynx thx小费;虽然defer解锁一个互斥体可以立即之前或之后调用锁是IMO 大多是一个非常好的主意; 确保您不仅可以忘记解锁,而且即使在非致命的紧急中功能时也可以解锁。
迈克·阿特拉斯

1
@RobbieV看起来此代码是线程安全/例程安全的,因为基础共享源已经是一个实现互斥量的LockedSource(golang.org/src/math/rand/rand.go:259)。
adityajones

130

您可以为此编写代码。如果要以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))
}

30
不要忘了rand.Seed(),否则每次启动时您都会得到相同的字符串... rand.Seed(time.Now()。UTC()。UnixNano())
Evan Lin

2
Evan的加法是正确的,但是还有其他类似的选择:rand.Seed(time.Now().Unix())rand.Seed(time.Now().UnixNano())
openwonk 2015年

7
对于难以猜测的秘密-密码,加密密钥等-切勿使用math/rand; 使用crypto/rand(例如@Not_A_Golfer的选项1)代替。
twotwotwo

1
@EvanLin这不会让人猜到吗?如果必须播种生成器,则攻击者可以猜测我播种的时间,并预测我生成的相同输出。
Matej

4
请注意,如果您尝试使用带种子的上述程序,则可以在操场上一直返回相同的结果。我在操场上尝试,一段时间后意识到了这一点。否则对我来说效果很好。希望它可以节省一些时间:)
Gaurav Sinha

18

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

免责声明:我是包裹的作者


1
顺便说一句:作者dchest是一位出色的开发人员,并制作了许多小型有用的程序包,例如:
Roshambo

16

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

  1. 您可以使用crypto/rand支持读取随机字节数组(从/ dev / urandom)并适合于加密随机生成的软件包。参见http://golang.org/pkg/crypto/rand/#example_Read。但是,它可能比正常的伪随机数生成慢。

  2. 取一个随机数,然后使用md5或类似的方式对其进行哈希处理。


4

在详细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


2

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

基数64的文本比基数256长1/3。(2 ^ 8与2 ^ 6; 8位/ 6位= 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
}

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

如果要使用十六进制,则基数16比基数256长2倍。(2 ^ 8与2 ^ 4; 8bits / 4bits = 2x ratio)

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
}

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

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

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


值得一提的是,Go Playground总是返回相同的随机数,因此您不会在该代码的不同执行中看到不同的随机字符串
TPPZ

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

为什么会生成n * 2 []byte
M. Rostami

1

这是我的方式)随意使用数学兰特或加密兰特。

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

0

如果您愿意在允许的字符池中添加一些字符,则可以使该代码与通过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
}

为什么有random % 64必要?
宋祖

2
因为len(encodeURL) == 64。如果random % 64未完成,则randomPos可能会> = 64,并在运行时引起紧急恐慌。
0xcaff

-2
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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.