在Go中生成长随机字符串的最快方法是什么?

时间:2012-10-07 19:09:31

标签: string random go

与[a-zA-Z0-9]字符串类似:

na1dopW129T0anN28udaZ

或十六进制字符串:

8c6f78ac23b4a7b8c0182d

我想说的是2K以上的字符。

6 个答案:

答案 0 :(得分:22)

您可以使用Go包uniuri生成随机字符串(或查看源代码以了解它们是如何进行的)。你会想要使用:

func NewLen(length int) string
  

NewLen返回提供长度的新随机字符串,由标准字符组成。

或者,要指定使用的字符集:

func NewLenChars(length int, chars []byte) string

答案 1 :(得分:21)

这在我的盒子上大约200MBps。有明显的改进空间。

type randomDataMaker struct {
    src rand.Source
}

func (r *randomDataMaker) Read(p []byte) (n int, err error) {
    for i := range p {
        p[i] = byte(r.src.Int63() & 0xff)
    }
    return len(p), nil
}

您只需使用io.CopyN即可生成所需的字符串。显然你可以在路上或其他任何地方调整字符集。

关于这个模型的好处是它只是一个io.Reader所以你可以使用它制作任何东西。

测试如下:

func BenchmarkRandomDataMaker(b *testing.B) {
    randomSrc := randomDataMaker{rand.NewSource(1028890720402726901)}
    for i := 0; i < b.N; i++ {
        b.SetBytes(int64(i))
        _, err := io.CopyN(ioutil.Discard, &randomSrc, int64(i))
        if err != nil {
            b.Fatalf("Error copying at %v: %v", i, err)
        }
    }
}

在我的2.2GHz i7的一个核心上:

BenchmarkRandomDataMaker       50000        246512 ns/op     202.83 MB/s

修改

自从我编写基准测试以来,我认为我会做一些明显的改进(不经常调用随机)。有了1/8的rand调用,它的运行速度提高了大约4倍,尽管这是一个很大的丑陋:

新版本:

func (r *randomDataMaker) Read(p []byte) (n int, err error) {
    todo := len(p)
    offset := 0
    for {
        val := int64(r.src.Int63())
        for i := 0; i < 8; i++ {
            p[offset] = byte(val & 0xff)
            todo--
            if todo == 0 {
                return len(p), nil
            }
            offset++
            val >>= 8
        }
    }

    panic("unreachable")
}

新基准:

BenchmarkRandomDataMaker      200000        251148 ns/op     796.34 MB/s

编辑2

将转换中的掩码取出为字节,因为它是多余的。快点好了:

BenchmarkRandomDataMaker      200000        231843 ns/op     862.64 MB/s

(这比实际工作叹息更容易

编辑3

今天在irc中出现了,所以我发布了一个库。此外,我的实际基准测试工具虽然对相对速度有用,但在报告中并不够准确。

我创建了randbo,您可以重复使用它来生成随机流,无论您需要它们。

答案 2 :(得分:15)

这实际上有点偏向于集合中的前8个字符(因为255不是len(alphanum)的倍数),但这将使你在那里大部分时间。

import (
    "crypto/rand"
)

func randString(n int) string {
    const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    var bytes = make([]byte, n)
    rand.Read(bytes)
    for i, b := range bytes {
        bytes[i] = alphanum[b % byte(len(alphanum))]
    }
    return string(bytes)
}

答案 3 :(得分:1)

在这里,Evan Shaw的回答重新开始,没有偏向于字符串的前8个字符。请注意,它使用了大量昂贵的big.Int操作,所以可能不是那么快!答案虽然非常强大,但仍然非常强大。

它使用rand.Int制作一个完全正确大小len(alphanum) ** n的整数,然后进行有效的基本转换为基础len(alphanum)

几乎可以肯定有一个更好的算法,它将涉及保留一个小得多的余数,并根据需要添加随机字节。这将摆脱昂贵的长整数运算。

import (
    "crypto/rand"
    "fmt"
    "math/big"
)

func randString(n int) string {
    const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    symbols := big.NewInt(int64(len(alphanum)))
    states := big.NewInt(0)
    states.Exp(symbols, big.NewInt(int64(n)), nil)
    r, err := rand.Int(rand.Reader, states)
    if err != nil {
        panic(err)
    }
    var bytes = make([]byte, n)
    r2 := big.NewInt(0)
    symbol := big.NewInt(0)
    for i := range bytes {
        r2.DivMod(r, symbols, symbol)
        r, r2 = r2, r
        bytes[i] = alphanum[symbol.Int64()]
    }
    return string(bytes)
}

答案 4 :(得分:0)

如果您想生成加密安全随机字符串,建议您查看this page。这是一个辅助函数,它从操作系统的随机源中读取n个随机字节,然后使用这些字节对其进行base64编码。请注意,由于base64,字符串长度将大于n

package main

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

func GenerateRandomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    _, err := rand.Read(b)
    if err != nil {
        return nil, err
    }

    return b, nil
}

func GenerateRandomString(s int) (string, error) {
    b, err := GenerateRandomBytes(s)
    return base64.URLEncoding.EncodeToString(b), err
}

func main() {
    token, _ := GenerateRandomString(32)
    fmt.Println(token)
}

答案 5 :(得分:-3)

我做了一些基准测试,Dustin的randbo包是我见过的最快的。我把它搞砸了并进行了一些优化,所以它现在更快了:GitHub