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

huangapple go评论81阅读模式
英文:

What is the fastest way to generate a long random string in Go?

问题

像 [a-zA-Z0-9] 字符串:

na1dopW129T0anN28udaZ

或十六进制字符串:

8c6f78ac23b4a7b8c0182d

通过长,我指的是2K及以上的字符。

英文:

Like [a-zA-Z0-9] string:

na1dopW129T0anN28udaZ

or hexadecimal string:

8c6f78ac23b4a7b8c0182d

By long I mean 2K and more characters.

答案1

得分: 24

This does about 200MBps on my box. There's obvious room for improvement.

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
}

You'd just use io.CopyN to produce the string you want. Obviously you could adjust the character set on the way in or whatever.

The nice thing about this model is that it's just an io.Reader so you can use it making anything.

Test is below:

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

On one core of my 2.2GHz i7:

BenchmarkRandomDataMaker   50000    246512 ns/op   202.83 MB/s

EDIT

Since I wrote the benchmark, I figured I'd do the obvious improvement thing (call out to the random less frequently). With 1/8 the calls to rand, it runs about 4x faster, though it's a big uglier:

New version:

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

New benchmark:

BenchmarkRandomDataMaker  200000    251148 ns/op   796.34 MB/s

EDIT 2

Took out the masking in the cast to byte since it was redundant. Got a good deal faster:

BenchmarkRandomDataMaker  200000    231843 ns/op   862.64 MB/s

(this is so much easier than real work sigh)

EDIT 3

This came up in irc today, so I released a library. Also, my actual benchmark tool, while useful for relative speed, isn't sufficiently accurate in its reporting.

I created randbo that you can reuse to produce random streams wherever you may need them.

英文:

This does about 200MBps on my box. There's obvious room for improvement.

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() &amp; 0xff)
	}
	return len(p), nil
}

You'd just use io.CopyN to produce the string you want. Obviously you could adjust the character set on the way in or whatever.

The nice thing about this model is that it's just an io.Reader so you can use it making anything.

Test is below:

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

On one core of my 2.2GHz i7:

BenchmarkRandomDataMaker	   50000	    246512 ns/op	 202.83 MB/s

EDIT

Since I wrote the benchmark, I figured I'd do the obvious improvement thing (call out to the random less frequently). With 1/8 the calls to rand, it runs about 4x faster, though it's a big uglier:

New version:

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 &lt; 8; i++ {
			p[offset] = byte(val &amp; 0xff)
			todo--
			if todo == 0 {
				return len(p), nil
			}
			offset++
			val &gt;&gt;= 8
		}
	}

	panic(&quot;unreachable&quot;)
}

New benchmark:

BenchmarkRandomDataMaker	  200000	    251148 ns/op	 796.34 MB/s

EDIT 2

Took out the masking in the cast to byte since it was redundant. Got a good deal faster:

BenchmarkRandomDataMaker	  200000	    231843 ns/op	 862.64 MB/s

(this is so much easier than real work sigh)

EDIT 3

This came up in irc today, so I released a library. Also, my actual benchmark tool, while useful for relative speed, isn't sufficiently accurate in its reporting.

I created randbo that you can reuse to produce random streams wherever you may need them.

答案2

得分: 22

你可以使用Go包uniuri来生成随机字符串(或查看源代码以了解它们是如何生成的)。你可以使用以下函数:

func NewLen(length int) string

> NewLen函数返回一个由标准字符组成的指定长度的新随机字符串。

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

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

You can use the Go package uniuri to generate random strings (or view the source code to see how they're doing it). You'll want to use:

func NewLen(length int) string

> NewLen returns a new random string of the provided length, consisting of standard characters.

Or, to specify the set of characters used:

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

答案3

得分: 17

这实际上对集合中的前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)
}
英文:

This is actually a little biased towards the first 8 characters in the set (since 255 is not a multiple of len(alphanum)), but this will get you most of the way there.

import (
    &quot;crypto/rand&quot;
)

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

答案4

得分: 3

如果你想生成加密安全的随机字符串,我建议你查看这个页面。这里有一个辅助函数,它从你的操作系统的随机源中读取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)
}
英文:

If you want to generate cryptographically secure random string, I recommend you to take a look at this page. Here is a helper function that reads n random bytes from the source of randomness of your OS and then use these bytes to base64encode it. Note that the string length would be bigger than n because of base64.

package main

import(
    &quot;crypto/rand&quot;
    &quot;encoding/base64&quot;
    &quot;fmt&quot;
)

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

得分: 1

这里是Evan Shaw的答案,重新修改了对字符串前8个字符的偏见。请注意,它使用了许多昂贵的big.Int操作,所以可能不是很快!但是答案在密码学上是强大的。

它使用rand.Int生成一个大小为len(alphanum) ** n的整数,然后进行了一个有效的基数转换,基数为len(alphanum)

几乎肯定有一个更好的算法,它可以保持一个更小的余数,并在需要时添加随机字节。这将消除昂贵的长整数运算。

import (
	&quot;crypto/rand&quot;
	&quot;fmt&quot;
	&quot;math/big&quot;
)

func randString(n int) string {
	const alphanum = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&quot;
	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)
}
英文:

Here Evan Shaw's answer re-worked without the bias towards the first 8 characters of the string. Note that it uses lots of expensive big.Int operations so probably isn't that quick! The answer is crypto strong though.

It uses rand.Int to make an integer of exactly the right size len(alphanum) ** n, then does what is effectively a base conversion into base len(alphanum).

There is almost certainly a better algorithm for this which would involve keeping a much smaller remainder and adding random bytes to it as necessary. This would get rid of the expensive long integer arithmetic.

import (
	&quot;crypto/rand&quot;
	&quot;fmt&quot;
	&quot;math/big&quot;
)

func randString(n int) string {
	const alphanum = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&quot;
	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)
}

huangapple
  • 本文由 发表于 2012年10月8日 03:09:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/12771930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定