Quero apenas uma sequência aleatória de caracteres (maiúsculas ou minúsculas), sem números, no Go. Qual é a maneira mais rápida e simples de fazer isso?
Quero apenas uma sequência aleatória de caracteres (maiúsculas ou minúsculas), sem números, no Go. Qual é a maneira mais rápida e simples de fazer isso?
Respostas:
A solução de Paul fornece uma solução simples e geral.
A pergunta pede o "caminho mais rápido e mais simples" . Vamos abordar a parte mais rápida também. Chegamos ao nosso código final mais rápido de maneira iterativa. O benchmarking de cada iteração pode ser encontrado no final da resposta.
Todas as soluções e o código de benchmarking podem ser encontrados no Go Playground . O código no Playground é um arquivo de teste, não um executável. Você precisa salvá-lo em um arquivo chamado XX_test.go
e executá-lo com
go test -bench . -benchmem
Prefácio :
A solução mais rápida não é uma solução essencial se você só precisa de uma sequência aleatória. Para isso, a solução de Paulo é perfeita. Isto é, se o desempenho importa. Embora as duas primeiras etapas ( bytes e restante ) possam ser um compromisso aceitável: elas melhoram o desempenho em 50% (consulte os números exatos na seção II. Benchmark ) e não aumentam significativamente a complexidade.
Dito isto, mesmo que você não precise da solução mais rápida, a leitura desta resposta pode ser aventureira e educativa.
Como lembrete, a solução geral original que estamos melhorando é a seguinte:
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
Se os caracteres para escolher e montar a sequência aleatória contiverem apenas as letras maiúsculas e minúsculas do alfabeto inglês, poderemos trabalhar com bytes apenas porque as letras do alfabeto inglês são mapeadas para os bytes 1 a 1 na codificação UTF-8 (que é como o Go armazena as strings).
Então, em vez de:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
podemos usar:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Ou melhor ainda:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Agora, isso já é uma grande melhoria: podemos conseguir que seja um const
(existem string
constantes, mas não existem constantes de fatia ). Como um ganho extra, a expressão len(letters)
também será a const
! (A expressão len(s)
é constante se s
for uma constante de sequência.)
E a que custo? Nada mesmo. string
s podem ser indexados, o que indexa seus bytes, perfeito, exatamente o que queremos.
Nosso próximo destino é assim:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
As soluções anteriores obtêm um número aleatório para designar uma letra aleatória chamando rand.Intn()
quais delegados para Rand.Intn()
quais delegados Rand.Int31n()
.
Isso é muito mais lento comparado ao rand.Int63()
que produz um número aleatório com 63 bits aleatórios.
Assim, poderíamos simplesmente chamar rand.Int63()
e usar o restante depois de dividir por len(letterBytes)
:
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
}
return string(b)
}
Isso funciona e é significativamente mais rápido, a desvantagem é que a probabilidade de todas as letras não será exatamente a mesma (supondo que rand.Int63()
produz todos os números de 63 bits com igual probabilidade). Embora a distorção seja extremamente pequena, pois o número de letras 52
é muito menor do que 1<<63 - 1
, na prática, isso é perfeitamente adequado.
Para facilitar a compreensão: digamos que você queira um número aleatório no intervalo de 0..5
. Usando 3 bits aleatórios, isso produziria os números 0..1
com probabilidade dupla do que a partir do intervalo 2..5
. Usando 5 bits aleatórios, os números no intervalo 0..1
ocorreriam com 6/32
probabilidade e os números no intervalo 2..5
com 5/32
probabilidade que agora estão mais próximos do desejado. Aumentar o número de bits torna isso menos significativo, ao atingir 63 bits, é insignificante.
Com base na solução anterior, podemos manter a distribuição igual de letras usando apenas o menor número de bits do número aleatório necessário para representar o número de letras. Por exemplo, se temos 52 cartas, que exige 6 bits para representá-lo: 52 = 110100b
. Portanto, usaremos apenas os 6 bits mais baixos do número retornado por rand.Int63()
. E para manter uma distribuição igual de letras, apenas "aceitamos" o número se ele estiver dentro do intervalo 0..len(letterBytes)-1
. Se os bits mais baixos forem maiores, descartamos e consultamos um novo número aleatório.
Observe que a chance dos bits mais baixos serem maiores ou iguais a len(letterBytes)
é menor do que 0.5
em geral ( 0.25
em média), o que significa que, mesmo que esse fosse o caso, repetir esse caso "raro" diminui a chance de não encontrar um bom número. Após a n
repetição, a chance de não termos um bom índice é muito menor do que pow(0.5, n)
, e essa é apenas uma estimativa superior. No caso de 52 letras, a chance de os 6 bits mais baixos não serem bons é apenas (64-52)/64 = 0.19
; o que significa, por exemplo, que as chances de não ter um bom número após 10 repetições são1e-8
.
Então, aqui está a solução:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)
func RandStringBytesMask(n int) string {
b := make([]byte, n)
for i := 0; i < n; {
if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i++
}
}
return string(b)
}
A solução anterior usa apenas os 6 bits mais baixos dos 63 bits aleatórios retornados por rand.Int63()
. Isso é um desperdício, pois obter os bits aleatórios é a parte mais lenta do nosso algoritmo.
Se tivermos 52 letras, isso significa que 6 bits codificam um índice de letras. Portanto, 63 bits aleatórios podem designar 63/6 = 10
diferentes índices de letras. Vamos usar todos esses 10:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func RandStringBytesMaskImpr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
O Masking Improved é muito bom, não podemos melhorar muito. Poderíamos, mas não vale a pena a complexidade.
Agora vamos encontrar outra coisa para melhorar. A fonte de números aleatórios.
Há um crypto/rand
pacote que fornece uma Read(b []byte)
função, então podemos usá-lo para obter quantos bytes com uma única chamada for necessário. Isso não ajudaria em termos de desempenho, poiscrypto/rand
implementa um gerador de números pseudo-aleatórios criptograficamente seguros, por isso é muito mais lento.
Então, vamos nos ater ao math/rand
pacote. O rand.Rand
usa a rand.Source
como fonte de bits aleatórios. rand.Source
é uma interface que especifica um Int63() int64
método: exatamente e a única coisa que precisamos e usamos em nossa solução mais recente.
Portanto, não precisamos realmente de um rand.Rand
(explícito ou global, compartilhado do rand
pacote), a rand.Source
é perfeitamente suficiente para nós:
var src = rand.NewSource(time.Now().UnixNano())
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
Observe também que esta última solução não exige que você inicialize (propague) o global Rand
do math/rand
pacote, pois ele não é usado (e nossarand.Source
é adequadamente inicializado / propagado).
Mais uma coisa a ser observada aqui: package doc of math/rand
states:
A fonte padrão é segura para uso simultâneo por várias goroutines.
Portanto, a fonte padrão é mais lenta que a Source
que pode ser obtida por rand.NewSource()
, porque a fonte padrão precisa fornecer segurança sob acesso / uso simultâneo, embora rand.NewSource()
não ofereça isso (e, portanto Source
, é provável que o retorno por ela seja mais rápido).
strings.Builder
Todas as soluções anteriores retornam um string
cujo conteúdo é construído primeiro em uma fatia ( []rune
no Genesis e []byte
nas soluções subseqüentes) e depois convertido em string
. Essa conversão final precisa fazer uma cópia do conteúdo da fatia, porque os string
valores são imutáveis e, se a conversão não fizer uma cópia, não é possível garantir que o conteúdo da sequência não seja modificado por meio da fatia original. Para detalhes, consulte Como converter a string utf8 em [] byte? e golang: [] byte (string) vs [] byte (* string) .
Vá 1.10 introduzido strings.Builder
. strings.Builder
um novo tipo que podemos usar para criar conteúdo string
semelhante bytes.Buffer
. Faz isso internamente usando ae []byte
, quando terminarmos, podemos obter o string
valor final usando seu Builder.String()
método. Mas o legal é que ele faz isso sem executar a cópia que acabamos de falar acima. Ousa fazer isso porque a fatia de bytes usada para criar o conteúdo da string não é exposta; portanto, é garantido que ninguém possa modificá-la intencionalmente ou maliciosamente para alterar a string "imutável" produzida.
Portanto, nossa próxima idéia é não criar a sequência aleatória em uma fatia, mas com a ajuda de a strings.Builder
, assim que terminarmos, podemos obter e retornar o resultado sem precisar fazer uma cópia dele. Isso pode ajudar em termos de velocidade e definitivamente ajudará em termos de uso e alocações de memória.
func RandStringBytesMaskImprSrcSB(n int) string {
sb := strings.Builder{}
sb.Grow(n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
sb.WriteByte(letterBytes[idx])
i--
}
cache >>= letterIdxBits
remain--
}
return sb.String()
}
Observe que, depois de criar um novo strings.Buidler
, chamamos seu Builder.Grow()
método, certificando-se de que ele aloca uma fatia interna grande o suficiente (para evitar realocações à medida que adicionamos as letras aleatórias).
strings.Builder
com o pacoteunsafe
strings.Builder
constrói a string em um interno []byte
, o mesmo que nós mesmos. Então, basicamente, fazê-lo via a strings.Builder
tem alguma sobrecarga, a única coisa que optamos strings.Builder
por é evitar a cópia final da fatia.
strings.Builder
evita a cópia final usando o pacote unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
O problema é que também podemos fazer isso sozinhos. Portanto, a idéia aqui é voltar a criar a sequência aleatória em a []byte
, mas quando terminar, não a converta string
em retorno, mas faça uma conversão insegura: obtenha uma string
que aponte para a nossa fatia de bytes como dados da sequência .
É assim que isso pode ser feito:
func RandStringBytesMaskImprSrcUnsafe(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}
rand.Read()
)Go 1.7 adicionou uma rand.Read()
função e um Rand.Read()
método. Devemos ser tentados a usá-los para ler quantos bytes forem necessários em uma única etapa, a fim de obter melhor desempenho.
Há um pequeno "problema" com isso: quantos bytes precisamos? Poderíamos dizer: até o número de letras de saída. Achamos que essa é uma estimativa superior, pois um índice de letras usa menos de 8 bits (1 byte). Mas, neste ponto, já estamos piorando (como obter os bits aleatórios é a "parte mais difícil") e estamos obtendo mais do que o necessário.
Observe também que, para manter uma distribuição igual de todos os índices de letras, pode haver alguns dados aleatórios "lixo" que não poderemos usar; portanto, acabaríamos pulando alguns dados e, assim, ficaríamos curtos quando passarmos por todos os a fatia de bytes. Nós precisaríamos obter mais bytes aleatórios "recursivamente". E agora estamos perdendo a rand
vantagem da "chamada única para empacotar" ...
Poderíamos "otimizar" o uso dos dados aleatórios dos quais adquirimos math.Rand()
. Podemos estimar quantos bytes (bits) precisaremos. 1 letra requer letterIdxBits
bits, e precisamos de n
letras; portanto, precisamos de n * letterIdxBits / 8.0
bytes arredondados para cima. Podemos calcular a probabilidade de um índice aleatório não ser utilizável (veja acima), para que possamos solicitar mais que "provavelmente" serão suficientes (se isso acontecer, repetimos o processo). Podemos processar a fatia de bytes como um "fluxo de bits", por exemplo, para o qual temos uma boa biblioteca de terceiros: github.com/icza/bitio
(divulgação: eu sou o autor).
Mas o código de referência ainda mostra que não estamos ganhando. Por que é tão?
A resposta para a última pergunta é porque rand.Read()
usa um loop e continua chamando Source.Int63()
até preencher a fatia passada. Exatamente o que a RandStringBytesMaskImprSrc()
solução faz, sem o buffer intermediário e sem a complexidade adicionada. É por isso que RandStringBytesMaskImprSrc()
permanece no trono. Sim, RandStringBytesMaskImprSrc()
usa uma rand.Source
diferença não sincronizada rand.Read()
. Mas o raciocínio ainda se aplica; e que é comprovado se usarmos em Rand.Read()
vez de rand.Read()
(o primeiro também não é sincronizado).
Tudo bem, é hora de comparar as diferentes soluções.
Momento da verdade:
BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op
BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op
Apenas mudando de runas para bytes, temos imediatamente um ganho de desempenho de 24% e o requisito de memória cai para um terço .
Livrar-se rand.Intn()
e usar em rand.Int63()
vez disso dá outro impulso de 20% .
O mascaramento (e a repetição em caso de grandes índices) diminui um pouco (devido a chamadas repetidas): -22% ...
Mas quando usamos todos (ou a maioria) dos 63 bits aleatórios (10 índices de uma rand.Int63()
chamada): isso acelera o tempo: 3 vezes .
Se concordarmos com um (não padrão, novo) em rand.Source
vez de rand.Rand
, ganharemos novamente 21%.
Se utilizamos strings.Builder
, ganhamos um pequeno 3,5% em velocidade , mas também alcançamos uma redução de 50% no uso e alocações de memória! Isso é bom!
Finalmente, se ousarmos usar o pacote em unsafe
vez de strings.Builder
, obteremos novamente bons 14% .
Comparando a solução final à inicial: RandStringBytesMaskImprSrcUnsafe()
é 6,3 vezes mais rápido que RandStringRunes()
, usa uma sexta memória e metade do número de alocações . Missão cumprida.
rand.Source
é usado. Uma solução melhor seria passar a rand.Source
para a RandStringBytesMaskImprSrc()
função e, dessa forma, nenhum bloqueio é necessário e, portanto, o desempenho / eficiência não é afetado. Cada goroutine poderia ter o seu próprio Source
.
defer
quando for óbvio que você não precisa. Veja grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
de desbloquear um mutex imediatamente antes ou depois de chamar um bloqueio, é principalmente uma idéia muito boa para a IMO ; você tem a garantia de que não se esqueça de desbloquear, mas também desbloqueie mesmo em uma função intermediária de pânico não fatal.
Você pode apenas escrever um código para ele. Esse código pode ser um pouco mais simples se você quiser confiar que as letras são todos bytes simples quando codificadas em 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))
}
rand.Seed(time.Now().Unix())
ourand.Seed(time.Now().UnixNano())
math/rand
; use crypto/rand
(como a opção 1 de @ Not_A_Golfer).
Duas opções possíveis (pode haver mais, é claro):
Você pode usar o crypto/rand
pacote que suporta a leitura de matrizes de bytes aleatórios (de / dev / urandom) e é voltado para a geração aleatória criptográfica. consulte http://golang.org/pkg/crypto/rand/#example_Read . Pode ser mais lento que a geração normal de números pseudo-aleatórios.
Pegue um número aleatório e faça o hash usando MD5 ou algo assim.
Após icza's
uma solução maravilhosamente explicada, aqui está uma modificação dela que usa em crypto/rand
vez de math/rand
.
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
}
Se você deseja uma solução mais genérica, que permita passar a fatia de bytes de caracteres para criar a cadeia de caracteres, tente usar o seguinte:
// 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)
}
Se você deseja transmitir sua própria fonte de aleatoriedade, seria trivial modificar o acima para aceitar um em io.Reader
vez de usar crypto/rand
.
Se você deseja números aleatórios criptograficamente seguros e o conjunto de caracteres exato é flexível (por exemplo, base64 é bom), é possível calcular exatamente qual o tamanho dos caracteres aleatórios necessários a partir do tamanho de saída desejado.
O texto da base 64 é 1/3 maior que a base 256. (proporção 2 ^ 8 vs 2 ^ 6; proporção 8bits / 6bits = 1,333)
import (
"crypto/rand"
"encoding/base64"
"math"
)
func randomBase64String(l int) string {
buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
rand.Read(buff)
str := base64.RawURLEncoding.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
Nota: você também pode usar RawStdEncoding se preferir + e / caracteres a - e _
Se você deseja hexadecimal, a base 16 é 2x mais longa que a base 256. (proporção 2 ^ 8 vs 2 ^ 4; proporção 8bits / 4bits = 2x)
import (
"crypto/rand"
"encoding/hex"
"math"
)
func randomBase16String(l int) string {
buff := make([]byte, int(math.Round(float64(l)/2)))
rand.Read(buff)
str := hex.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
No entanto, você pode estender isso para qualquer conjunto de caracteres arbitrário se tiver um codificador base256 para baseN para o seu conjunto de caracteres. Você pode fazer o mesmo cálculo de tamanho com quantos bits são necessários para representar seu conjunto de caracteres. O cálculo da taxa para qualquer conjunto de caracteres arbitrário é:) ratio = 8 / log2(len(charset))
.
Embora essas duas soluções sejam seguras, simples, devem ser rápidas e não desperdiçar seu pool de entropia criptográfica.
Aqui está o playground mostrando que funciona para qualquer tamanho. https://play.golang.org/p/i61WUVR8_3Z
func Rand(n int) (str string) {
b := make([]byte, n)
rand.Read(b)
str = fmt.Sprintf("%x", b)
return
}
[]byte
?
Se você deseja adicionar alguns caracteres ao seu conjunto de caracteres permitidos, você pode fazer o código funcionar com qualquer coisa que forneça bytes aleatórios por meio de um io.Reader. Aqui estamos usando crypto/rand
.
// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
output := make([]byte, n)
// We will take n bytes, one byte for each character of output.
randomness := make([]byte, n)
// read all random
_, err := rand.Read(randomness)
if err != nil {
panic(err)
}
// fill output
for pos := range output {
// get random item
random := uint8(randomness[pos])
// random % 64
randomPos := random % uint8(len(encodeURL))
// put into output
output[pos] = encodeURL[randomPos]
}
return output
}
random % 64
necessário?
len(encodeURL) == 64
. Se random % 64
não foi feito, randomPos
pode ser> = 64 e causar pânico fora dos limites em tempo de execução.
/*
korzhao
*/
package rand
import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)
// Doesn't share the rand library globally, reducing lock contention
type Rand struct {
Seed int64
Pool *sync.Pool
}
var (
MRand = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)
// init random number generator
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}
// get the seed
func getSeed() int64 {
return time.Now().UnixNano()
}
func (s *Rand) getrand() *rand.Rand {
return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
s.Pool.Put(r)
}
// get a random number
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)
return r.Intn(n)
}
// bulk get random numbers
func (s *Rand) Read(p []byte) (int, error) {
r := s.getrand()
defer s.putrand(r)
return r.Read(p)
}
func CreateRandomString(len int) string {
b := make([]byte, len)
_, err := MRand.Read(b)
if err != nil {
return ""
}
for i := 0; i < len; i++ {
b[i] = randlist[b[i]%(62)]
}
return *(*string)(unsafe.Pointer(&b))
}
24,0 ns / op 16 B / op 1 aloca /
const (
chars = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
charsLen = len(chars)
mask = 1<<6 - 1
)
var rng = rand.NewSource(time.Now().UnixNano())
// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
/* chars 38个字符
* rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
*/
buf := make([]byte, ln)
for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
if remain == 0 {
cache, remain = rng.Int63(), 10
}
buf[idx] = chars[int(cache&mask)%charsLen]
cache >>= 6
remain--
idx--
}
return *(*string)(unsafe.Pointer(&buf))
}
BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 alocações / op