我试图在Go中生成一个随机字符串,这是我到目前为止编写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
   "bytes"
   "fmt"
   "math/rand"
   "time"
)

func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    rand.Seed(time.Now().UTC().UnixNano())
    return min + rand.Intn(max-min)
}

我的执行速度很慢。 使用time进行播种会在一定时间内带来相同的随机数,因此循环会一次又一次地迭代。 如何改善我的代码?

  • " if string(randInt(65,90))!= temp {"看起来像是您试图增加额外的安全性,但是,事情之间偶然发生了同样的事情。 通过这样做,您实际上可能在降低熵。
  • 作为附带说明,无需在" time.Now()。UTC()。UnixNano()"中转换为UTC。 Unix时间是从Epoch开始计算的,Epoch始终是UTC。
  • 您应该设置一次种子,只能设置一次,并且绝不能超过一次。 好吧,如果您的应用程序可以运行数天,则可以每天设置一次。
  • 您应该播种一次。 我想" Z"可能永远不会出现,我猜呢? 因此,我更喜欢使用开始索引包含和结束索引排除。

每次设置相同的种子,您将获得相同的序列。因此,当然,如果您将种子设置为快速循环中的时间,则可能会多次调用相同的种子。

就您而言,在您调用randInt函数直到您拥有不同的值时,您正在等待时间(由Nano返回)改变。

对于所有伪随机库,您只需设置一次种子,例如在初始化程序时,除非您特别需要重现给定的序列(通常仅用于调试和单元测试)。

之后,您只需调用Intn即可获得下一个随机整数。

rand.Seed(time.Now().UTC().UnixNano())行从randInt函数移至main的开头,一切将变得更快。

还请注意,我认为您可以简化字符串的构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    fmt.Println(randomString(10))
}

func randomString(l int) string {
    bytes := make([]byte, l)
    for i := 0; i < l; i++ {
        bytes[i] = byte(randInt(65, 90))
    }
    return string(bytes)
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}
  • 感谢您的解释,我认为这需要每次播种。
  • 您也可以将rand.Seed(...)添加到功能init()中。 init()main()之前被自动调用。请注意,您不需要从main()调用init()
  • @Jabba对。我的回答尽可能简单,离问题不太远,但是您的观察是正确的。
  • 非常精确的解释。
  • 请注意,到目前为止,尚未发布任何答案都不能以加密安全的方式初始化种子。根据您的应用程序,这可能根本不重要,或者可能导致灾难性故障。
  • 您编写的randInt函数不包括max。应该是:return min + rand.Intn(max - min + 1)
  • @DomenicD。这是标准做法。您不会找到任何不排除最大范围的标准随机值函数。
  • 在这里,亲爱的互联网陌生人,有一些假互联网点,您已经真正解决了问题。应该指出,这在OAuth方案中非常有用。
  • 无论如何,@ IngoBlechschmidt mathrand都不是加密安全的。如果这是必需的,则应使用cryptorand

我不明白为什么人们在播种时间价值。以我的经验,这从来不是一个好主意。例如,虽然系统时钟可能以纳秒表示,但系统的时钟精度不是纳秒。

该程序不应在Go操场上运行,但是如果您在计算机上运行该程序,则可以粗略估算出可以期望的精度类型。我看到约1000000 ns的增量,所以1毫秒的增量。那是20位未使用的熵。一直以来,高位大多是恒定的。

这对您很重要,程度会有所不同,但是您只需使用crypto/rand.Read作为种子的来源,就可以避免基于时钟的种子值的陷阱。它将为您提供您可能会在随机数中寻找的不确定性质量(即使实际实现本身仅限于一组不同且确定性的随机序列)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
    crypto_rand"crypto/rand"
   "encoding/binary"
    math_rand"math/rand"
)

func init() {
    var b [8]byte
    _, err := crypto_rand.Read(b[:])
    if err != nil {
        panic("cannot seed math/rand package with cryptographically secure random number generator")
    }
    math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:])))
}

作为附带说明,但与您的问题有关。您可以使用此方法创建自己的rand.Source,以避免使用锁来保护源代码的开销。 rand软件包实用程序功能很方便,但它们也使用了内部的锁来防止同时使用源。如果不需要,可以通过创建自己的Source并以非并行方式使用它来避免它。无论如何,您不应该在迭代之间重新使用随机数生成器,它永远不会被设计为以这种方式使用。

  • 这个答案很不为人知。特别是对于可能在一秒钟内运行多次的命令行工具,这是必须做的。谢谢

只是为了后代而已:有时候使用初始字符集字符串生成随机字符串可能会更好。如果该字符串应该由人工手动输入,则很有用;排除0,O,1和l可以帮助减少用户错误。

1
2
3
4
5
6
7
8
9
10
var alpha ="abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"

// generates a random string of fixed size
func srand(size int) string {
    buf := make([]byte, size)
    for i := 0; i < size; i++ {
        buf[i] = alpha[rand.Intn(len(alpha))]
    }
    return string(buf)
}

我通常将种子设置在init()块内。它们记录在这里:http://golang.org/doc/effective_go.html#init

  • 据我正确理解,在rand.Intn(len(alpha)-1)中不需要有-1。这是因为rand.Intn(n)总是返回小于n的数字??(换句话说:从零到n-1(包括零))。
  • @snap是正确的;实际上,在len(alpha)-1中包含-1可以确保序列中从不使用数字9。
  • 还应注意,排除0(零)是个好主意,因为您将字节片转换为字符串,这导致0变为空字节。例如,尝试创建一个中间为0字节的文件,看看会发生什么。

好吧,为什么这么复杂!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
    rand.Seed( time.Now().UnixNano())
    var bytes int

    for i:= 0 ; i < 10 ; i++{
        bytes = rand.Intn(6)+1
        fmt.Println(bytes)
        }
    //fmt.Println(time.Now().UnixNano())
}

这是基于dystroy的代码,但符合我的需要。

死了六个(rands int 1 =< i =< 6)

1
2
3
4
5
func randomInt (min int , max int  ) int {
    var bytes int
    bytes = min + rand.Intn(max)
    return int(bytes)
}

上面的功能是完全一样的。

我希望这些信息有用。

  • 这将始终以相同的顺序(如果多次调用)以相同的顺序返回,这在我看来并不是很随机。查看实时示例:play.golang.org/p/fHHENtaPv5 3 5 2 5 4 2 5 6 3 1
  • @ThomasModeneis:那是因为他们在操场上假时间。
  • 谢谢@ofavre,那个假时间真的让我很头疼。
  • 在调用rand.Intn()之前,您仍然需要播种,否则,每次运行程序时,总会得到相同的数字。
  • var bytes int有什么原因吗?将上面的bytes = rand.Intn(6)+1更改为bytes := rand.Intn(6)+1有什么区别?他们俩似乎都为我工作,出于某种原因,其中之一不是最理想的吗?

如果您的目的只是生成一个随机数的字符串,那么我认为没有必要将它与多个函数调用复杂化或每次都重置种子。

最重要的步骤是在实际运行rand.Init(x)之前仅调用一次种子函数。 Seed使用提供的种子值将默认Source初始化为确定性状态。因此,建议在实际函数调用伪随机数生成器之前先调用它一次。

这是创建随机数字符串的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
   "fmt"
   "math/rand"
   "time"
)



func main(){
    rand.Seed(time.Now().UnixNano())

    var s string
    for i:=0;i<10;i++{
    s+=fmt.Sprintf("%d",rand.Intn(7))
    }
    fmt.Printf(s)
}

我使用Sprintf的原因是因为它允许简单的字符串格式设置。

同样,在rand.Intn(7)中,Intn以int形式返回[0,7)中的非负伪随机数。


这是十亿分之一秒,两次获得相同种子的机会是多少。
无论如何,感谢您的帮助,这是我基于所有输入的最终解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
   "math/rand"
   "time"
)

func init() {
    rand.Seed(time.Now().UTC().UnixNano())
}

// generates a random string
func srand(min, max int, readable bool) string {

    var length int
    var char string

    if min < max {
        length = min + rand.Intn(max-min)
    } else {
        length = min
    }

    if readable == false {
        char ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    } else {
        char ="ABCDEFHJLMNQRTUVWXYZabcefghijkmnopqrtuvwxyz23479"
    }

    buf := make([]byte, length)
    for i := 0; i < length; i++ {
        buf[i] = char[rand.Intn(len(char)-1)]
    }
    return string(buf)
}

// For testing only
func main() {
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, true))
    println(srand(5, 5, false))
    println(srand(5, 7, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 10, false))
    println(srand(5, 50, true))
    println(srand(5, 4, true))
    println(srand(5, 400, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
    println(srand(6, 5, true))
}
  • 回复:what are the chances of getting the exact the exact same [nanosecond] twice?很好。这一切都取决于golang运行时实现的内部精度。即使单位是纳秒,最小的增量也可能是毫秒或什至秒。

由于golang api的更改而导致的小更新,请省略.UTC():

time.Now()。UTC()。UnixNano()-> time.Now()。UnixNano()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(randomInt(100, 1000))
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}