在日常开发中,生成随机数是很常见的需求,Go 语言有两种方式来生成随机数,分别由 math/rand
和 crypo/rand
库来提供。
为什么会提供两种方式呢?其实两者是有区别的,math
提供的是伪随机数,生成的随机序列不是真正的随机;而 crypo
提供的随机数具有更好的随机性,可以满足密码对随机数的要求,但缺点是性能较差,据资料显示相差 10 倍左右。
(一)math/rand
伪随机数生成的随机数是确定的。相同的程序不管什么时间、在什么机器上执行,生成的随机数序列都是相同的。
1 2 3 4 5 6 7 8 9 |
func main() { for i:=0; i<10; i++ { fmt.Printf("%d ", rand.Intn(10)) } } $ go run main.go 1 7 7 9 1 8 5 0 6 0 $ go run main.go 1 7 7 9 1 8 5 0 6 0 |
可以看到,程序执行多次产生的随机数是一样的,不够随机。我们可以通过设置随机种子,也可以理解为随机函数增加参数。
1 2 3 4 5 6 7 8 9 10 |
func main() { rand.Seed(1009) for i:=0; i<10; i++ { fmt.Printf("%d ", rand.Intn(10)) } } $ go run main.go 9 4 0 6 5 6 1 5 4 9 $ go run main.go 9 4 0 6 5 6 1 5 4 9 |
在生成随机数之前,调用 Seed
函数设置随机数种子,可以发现生成的随机数终于发生了变化。但重新执行程序时,生成的随机序列仍然是一样的。
这里有个问题,只要 Seed
种子一样,每次程序启动都是按照一样的随机数去生成。为了更加随机,我们可以把种子设置为时间戳。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func main() { rand.Seed(time.Now().UnixNano()) for i:=0; i<10; i++ { fmt.Printf("%d ", rand.Intn(10)) } } $ go run main.go 2 4 2 8 5 7 0 0 4 0 $ go run main.go 2 1 9 2 7 3 0 1 4 8 $ go run main.go 0 3 8 3 9 2 7 3 5 6 |
可以看到,基本上能够实现了随机数的生成,可以满足日常大多数工作需求。
(二)crypto/rand
crypto
提供真正的随机数,能够实现更好的随机性,可以满足密码对随机数的要求,但缺点就是性能比较慢
1 2 3 4 5 6 |
func main() { for i := 0; i < 10; i++ { n, _ := rand.Int(rand.Reader, big.NewInt(100)) println(n.Int64()) } } |
如上述代码,使用非常简单。据相关测试实验表明,跟 math
提供随机数性能相比,要差 10 倍左右。
两者各有优缺点,需要根据实际需求来选择即可。对于涉及密码类的开发工作要选用 crypto/rand
,对于不涉及密码类开发的工作,可以选择 math/rand
一般就能满足常规随机数的需求。