我试图在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)
}

我的实现非常缓慢。使用时间的播种在特定的时间内带来相同的随机数,因此循环一次又一次地迭代。我如何改进我的代码?


当前回答

随着Go 1.20 (Q4 2022),种子随机数生成器的正确方法也可以是…什么都不做。

如果没有调用Seed,生成器将在程序启动时随机播种。

“math/rand:随机种子全局生成器”提案已被接受(2022年10月),并已开始实施:

CL 443058: math/rand:自动播种全局源代码

实现提案#54880,以自动播种全局源代码。

The justification for this not being a breaking change is that any use of the global source in a package's init function or exported API clearly must be valid - that is, if a package changes how much randomness it consumes at init time or in an exported API, that clearly isn't the kind of breaking change that requires issuing a v2 of that package. That kind of per-package change in the position of the global source is indistinguishable from seeding the global source differently. So if the per-package change is valid, so is auto-seeding. And then, of course, auto-seeding means that packages will be far less likely to depend on the specific results of the global source and therefore not break when those kinds of per-package changes happen in the future. Seed(1) can be called in programs that need the old sequence from the global source and want to restore the old behavior. Of course, those programs will still be broken by the per-package changes just described, and it would be better for them to allocate local sources rather than continue to use the global one.


在issue 20661和CL 436955中,还要注意math/rand。Read已弃用:对于几乎所有用例,crypto/rand。读更合适。

如下所示:

我们可以像这样用gosec linter和golanglint-ci 并注意G404代码: goangci -lint run——disable-all——启用gosec

其他回答

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

这个程序不应该在围棋操场上运行,但如果你在你的机器上运行它,你就可以大致估计你可以期望的精度类型。我看到了大约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!

@[Denys Séguret]已张贴正确。但在我的情况下,我需要新的种子,因此每次低于代码;

以防你需要快速函数。我这样用。


func RandInt(min, max int) int {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return r.Intn(max-min) + min
}

func RandFloat(min, max float64) float64 {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    return min + r.Float64()*(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

只是为了子孙后代:有时使用初始字符集字符串生成随机字符串更可取。如果字符串应该由人类手动输入,这是有用的;排除0、O、1、l可以减少用户错误。

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.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)中的非负伪随机数。