在Go中,我只想要一个随机字符串(大写或小写),没有数字。最快最简单的方法是什么?


当前回答

package main

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

// customEncodeURL is like `bas64.encodeURL` 
// except its made up entirely of uppercase characters:
const customEncodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKL"

// Random generates a random string.
// It is not cryptographically secure.
func Random(n int) string {
    b := make([]byte, n)
    rand.Seed(time.Now().UnixNano())
    _, _ = rand.Read(b) // docs say that it always returns a nil error.

    customEncoding := base64.NewEncoding(customEncodeURL).WithPadding(base64.NoPadding)
    return customEncoding.EncodeToString(b)
}

func main() {
    fmt.Println(Random(16))
}

其他回答

作为icza的出色解决方案的后续,下面我使用rand。读者

func RandStringBytesMaskImprRandReaderUnsafe(length uint) (string, error) {
    const (
        charset     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        charIdxBits = 6                  // 6 bits to represent a letter index
        charIdxMask = 1<<charIdxBits - 1 // All 1-bits, as many as charIdxBits
        charIdxMax  = 63 / charIdxBits   // # of letter indices fitting in 63 bits
    )

    buffer := make([]byte, length)
    charsetLength := len(charset)
    max := big.NewInt(int64(1 << uint64(charsetLength)))

    limit, err := rand.Int(rand.Reader, max)
    if err != nil {
        return "", err
    }

    for index, cache, remain := int(length-1), limit.Int64(), charIdxMax; index >= 0; {
        if remain == 0 {
            limit, err = rand.Int(rand.Reader, max)
            if err != nil {
                return "", err
            }

            cache, remain = limit.Int64(), charIdxMax
        }

        if idx := int(cache & charIdxMask); idx < charsetLength {
            buffer[index] = charset[idx]
            index--
        }

        cache >>= charIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&buffer)), nil
}


func BenchmarkBytesMaskImprRandReaderUnsafe(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()

    const length = 16

    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            RandStringBytesMaskImprRandReaderUnsafe(length)
        }
    })
}

两个可能的选项(当然可能有更多):

您可以使用crypto/rand包,它支持读取随机字节数组(来自/dev/urandom),并面向加密随机生成。见http://golang.org/pkg/crypto/rand/#example_Read。它可能比普通的伪随机数生成慢。 取一个随机数,用md5或类似的方法进行哈希。

你可以为它编写代码。如果您希望使用UTF-8编码时所有字母都是单个字节,则此代码可以稍微简单一些。

package main

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

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randSeq(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]
    }
    return string(b)
}

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

    fmt.Println(randSeq(10))
}

这里是一个简单而高效的加密安全随机字符串的解决方案。

package main

import (
    "crypto/rand"
    "unsafe"
    "fmt"
)

var alphabet = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func main() {
    fmt.Println(generate(16))
}

func generate(size int) string {
    b := make([]byte, size)
    rand.Read(b)
    for i := 0; i < size; i++ {
        b[i] = alphabet[b[i] % byte(len(alphabet))]
    }
    return *(*string)(unsafe.Pointer(&b))
}

基准

Benchmark  95.2 ns/op      16 B/op      1 allocs/op

在icza精彩解释的解决方案之后,这里是它的一个修改,使用加密/兰特而不是数学/兰特。

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

如果你想要一个更通用的解决方案,它允许你传入字符字节的切片来创建字符串,你可以尝试使用这个:

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

如果你想传入你自己的随机源,修改上面的内容来接受io是很简单的。Reader而不是使用crypto/rand。