我试图在Go中生成一个随机字符串,这是我迄今为止写的代码:
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)
}
我的实现非常缓慢。使用时间的播种在特定的时间内带来相同的随机数,因此循环一次又一次地迭代。我如何改进我的代码?
我尝试了下面的程序,每次都看到不同的字符串
package main
import (
"fmt"
"math/rand"
"time"
)
func RandomString(count int){
rand.Seed(time.Now().UTC().UnixNano())
for(count > 0 ){
x := Random(65,91)
fmt.Printf("%c",x)
count--;
}
}
func Random(min, max int) (int){
return min+rand.Intn(max-min)
}
func main() {
RandomString(12)
}
控制台的输出是
D:\james\work\gox>go run rand.go
JFBYKAPEBCRC
D:\james\work\gox>go run rand.go
VDUEBIIDFQIB
D:\james\work\gox>go run rand.go
VJYDQPVGRPXM
我不明白为什么人们要用时间值来播种。根据我的经验,这从来都不是一个好主意。例如,虽然系统时钟可能以纳秒表示,但系统的时钟精度不是纳秒。
这个程序不应该在围棋操场上运行,但如果你在你的机器上运行它,你就可以大致估计你可以期望的精度类型。我看到了大约1000000 ns的增量,也就是1ms的增量。有20比特的熵没有被使用。而高比特大部分是恒定的!?一天大约有24比特的熵,这是非常残酷的(这可能会产生漏洞)。
这对你的影响程度会有所不同,但你可以通过简单地使用crypto/rand来避免基于时钟的种子值的陷阱。阅读是你种子的来源。它将为您提供可能在随机数中寻找的非确定性质量(即使实际实现本身仅限于一组不同且确定的随机序列)。
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[:])))
}
As a side note but in relation to your question. You can create your own rand.Source using this method to avoid the cost of having locks protecting the source. The rand package utility functions are convenient but they also use locks under the hood to prevent the source from being used concurrently. If you don't need that you can avoid it by creating your own Source and use that in a non-concurrent way. Regardless, you should NOT be reseeding your random number generator between iterations, it was never designed to be used that way.
Edit: I used to work in ITAM/SAM and the client we built (then) used a clock based seed. After a Windows update a lot of machines in the company fleet rebooted at roughly the same time. This caused an involtery DoS attack on upstream server infrastructure because the clients was using system up time to seed randomness and these machines ended up more or less randomly picking the same time slot to report in. They were meant to smear the load over a period of an hour or so but that did not happen. Seed responsbily!
每次你设置相同的种子,你会得到相同的序列。当然,如果你在一个快速循环中设置时间的种子,你可能会用同一个种子多次调用它。
在你的例子中,当你调用randInt函数直到你有一个不同的值,你在等待时间(由Nano返回)改变。
对于所有伪随机库,您只需设置一次种子,例如在初始化程序时,除非您特别需要重新生成给定的序列(这通常只用于调试和单元测试)。
在此之后,您只需调用Intn来获得下一个随机整数。
将rand.Seed(time.Now(). utc (). unixnano())行从randInt函数移动到main函数的开头,一切都会更快。失去.UTC()调用,因为:
UnixNano返回t作为Unix时间,即自UTC 1970年1月1日以来经过的纳秒数。
还要注意,我认为你可以简化你的字符串构建:
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().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.Init(x)之前只调用一次种子函数。Seed使用提供的种子值将默认Source初始化为确定状态。因此,建议在实际函数调用伪随机数生成器之前调用它一次。
下面是创建随机数字符串的示例代码
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的原因是它允许简单的字符串格式化。
此外,In rand.Intn(7) Intn作为int返回[0,7)中的非负伪随机数。