Voglio solo una stringa casuale di caratteri (maiuscoli o minuscoli), senza numeri, in Go. Qual è il modo più veloce e semplice per farlo?
Voglio solo una stringa casuale di caratteri (maiuscoli o minuscoli), senza numeri, in Go. Qual è il modo più veloce e semplice per farlo?
Risposte:
La soluzione di Paul fornisce una soluzione semplice e generale.
La domanda chiede "il modo più veloce e semplice" . Affrontiamo anche la parte più veloce . Arriveremo al nostro codice finale, il più veloce in modo iterativo. Il benchmarking di ogni iterazione può essere trovato alla fine della risposta.
Tutte le soluzioni e il codice di benchmarking sono disponibili nel Go Playground . Il codice nel Playground è un file di prova, non un eseguibile. Devi salvarlo in un file chiamato XX_test.go
ed eseguirlo
go test -bench . -benchmem
Prefazione :
La soluzione più veloce non è una soluzione di riferimento se hai solo bisogno di una stringa casuale. Per questo, la soluzione di Paul è perfetta. Questo è se le prestazioni sono importanti. Sebbene i primi 2 passaggi ( byte e resto ) possano essere un compromesso accettabile: migliorano le prestazioni di circa il 50% (vedere i numeri esatti nella sezione II. Benchmark ) e non aumentano la complessità in modo significativo.
Detto questo, anche se non hai bisogno della soluzione più veloce, leggere questa risposta potrebbe essere avventuroso ed educativo.
Come promemoria, la soluzione originale e generale che stiamo migliorando è questa:
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 i caratteri tra cui scegliere e assemblare la stringa casuale contengono solo le lettere maiuscole e minuscole dell'alfabeto inglese, possiamo lavorare con i byte solo perché le lettere dell'alfabeto inglese mappano i byte da 1 a 1 nella codifica UTF-8 (che è come Go archivia le stringhe).
Quindi invece di:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
possiamo usare:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
O ancora meglio:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Ora questo è già un grande miglioramento: potremmo ottenere che sia un const
(ci sono string
costanti ma non ci sono costanti di fetta ). Come guadagno extra, anche l'espressione len(letters)
sarà a const
! (L'espressione len(s)
è costante se s
è una costante di stringa.)
E a quale costo? Niente di niente. string
s può essere indicizzato che indicizza i suoi byte, perfetto, esattamente quello che vogliamo.
La nostra prossima destinazione si presenta così:
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)
}
Le soluzioni precedenti ottengono un numero casuale per designare una lettera casuale chiamando i rand.Intn()
delegati a Rand.Intn()
quali delegati Rand.Int31n()
.
Questo è molto più lento rispetto al rand.Int63()
quale produce un numero casuale con 63 bit casuali.
Quindi potremmo semplicemente chiamare rand.Int63()
e usare il resto dopo aver diviso per 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)
}
Questo funziona ed è significativamente più veloce, lo svantaggio è che la probabilità di tutte le lettere non sarà esattamente la stessa (supponendo che rand.Int63()
produca tutti i numeri a 63 bit con uguale probabilità). Anche se la distorsione è estremamente ridotta in quanto il numero di lettere 52
è molto più piccolo di 1<<63 - 1
, quindi in pratica questo va benissimo.
Per renderlo più semplice: supponiamo che tu voglia un numero casuale nell'intervallo di 0..5
. Usando 3 bit casuali, questo produrrebbe i numeri 0..1
con doppia probabilità rispetto all'intervallo 2..5
. Usando 5 bit casuali, 0..1
si verificherebbero numeri nell'intervallo con 6/32
probabilità e numeri nell'intervallo 2..5
con 5/32
probabilità che ora è più vicino al desiderato. Aumentare il numero di bit rende questo meno significativo, quando si raggiunge 63 bit, è trascurabile.
Basandoci sulla soluzione precedente, possiamo mantenere la distribuzione equa delle lettere utilizzando solo il numero più basso di bit del numero casuale quanti ne sono necessari per rappresentare il numero di lettere. Così per esempio se abbiamo 52 lettere, richiede 6 bit per rappresentare esso: 52 = 110100b
. Quindi utilizzeremo solo i 6 bit più bassi del numero restituito da rand.Int63()
. E per mantenere un'equa distribuzione delle lettere, "accettiamo" il numero solo se rientra nell'intervallo 0..len(letterBytes)-1
. Se i bit più bassi sono maggiori, lo scartiamo e richiediamo un nuovo numero casuale.
Si noti che la probabilità che i bit più bassi siano maggiori o uguali a len(letterBytes)
è inferiore rispetto 0.5
al generale ( 0.25
in media), il che significa che, anche se così fosse, ripetere questo caso "raro" diminuisce la possibilità di non trovare un buon numero. Dopo la n
ripetizione, la possibilità che non abbiamo un buon indice è molto inferiore pow(0.5, n)
, e questa è solo una stima superiore. Nel caso di 52 lettere la possibilità che i 6 bit più bassi non siano buoni è solo (64-52)/64 = 0.19
; il che significa ad esempio che è probabile che non ci sia un buon numero dopo 10 ripetizioni 1e-8
.
Quindi ecco la soluzione:
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)
}
La soluzione precedente utilizza solo i 6 bit più bassi dei 63 bit casuali restituiti da rand.Int63()
. Questo è uno spreco poiché ottenere i bit casuali è la parte più lenta del nostro algoritmo.
Se abbiamo 52 lettere, ciò significa che 6 bit codificano un indice di lettere. Quindi 63 bit casuali possono designare 63/6 = 10
diversi indici di lettere. Usiamo tutti quei 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)
}
Il mascheramento migliorato è abbastanza buono, non possiamo migliorarlo molto. Potremmo, ma non ne vale la pena.
Ora troviamo qualcos'altro da migliorare. La fonte di numeri casuali.
Esiste un crypto/rand
pacchetto che fornisce una Read(b []byte)
funzione, quindi potremmo usarlo per ottenere tanti byte con una sola chiamata quanti ne abbiamo bisogno. Ciò non aiuterebbe in termini di prestazioni in quanto crypto/rand
implementa un generatore di numeri pseudocasuali crittograficamente sicuro, quindi è molto più lento.
Quindi atteniamoci al math/rand
pacchetto. Il rand.Rand
utilizza una rand.Source
come sorgente di bit casuali. rand.Source
è un'interfaccia che specifica un Int63() int64
metodo: esattamente e l'unica cosa di cui avevamo bisogno e che abbiamo usato nella nostra ultima soluzione.
Quindi non abbiamo davvero bisogno di un rand.Rand
(esplicito o globale, condiviso del rand
pacchetto), un rand.Source
è abbastanza perfetto per noi:
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)
}
Si noti inoltre che quest'ultima soluzione non richiede l'inizializzazione (seed) del globale Rand
del math/rand
pacchetto in quanto non utilizzato (e il nostro rand.Source
è correttamente inizializzato / seeded).
Un'altra cosa da notare qui: pacchetto doc di math/rand
stati:
La fonte predefinita è sicura per l'uso simultaneo da parte di più goroutine.
Quindi la fonte predefinita è più lenta di Source
quella che può essere ottenuta da rand.NewSource()
, perché la fonte predefinita deve fornire sicurezza in caso di accesso / uso simultaneo, mentre rand.NewSource()
non lo offre (e quindi Source
è più probabile che la restituzione da essa sia più veloce).
strings.Builder
Tutte le soluzioni precedenti restituiscono a il string
cui contenuto viene prima creato in una sezione ( []rune
in Genesi e []byte
nelle soluzioni successive), quindi convertito in string
. Questa conversione finale deve creare una copia del contenuto della sezione, poiché i string
valori sono immutabili e se la conversione non ne crea una copia, non è possibile garantire che il contenuto della stringa non venga modificato tramite la sezione originale. Per i dettagli, vedere Come convertire la stringa utf8 in [] byte? e golang: [] byte (stringa) vs [] byte (* stringa) .
Go 1.10 introdotto strings.Builder
. strings.Builder
un nuovo tipo che possiamo usare per creare contenuti di un string
simile bytes.Buffer
. Lo fa internamente usando a []byte
, e quando abbiamo finito, possiamo ottenere il string
valore finale usando il suo Builder.String()
metodo. Ma la cosa interessante è che lo fa senza eseguire la copia di cui abbiamo appena parlato sopra. Osa farlo perché la porzione di byte utilizzata per costruire il contenuto della stringa non è esposta, quindi è garantito che nessuno può modificarlo involontariamente o maliziosamente per alterare la stringa "immutabile" prodotta.
Quindi la nostra prossima idea è di non costruire la stringa casuale in una sezione, ma con l'aiuto di a strings.Builder
, quindi una volta che abbiamo finito, possiamo ottenere e restituire il risultato senza doverne fare una copia. Questo può aiutare in termini di velocità e sicuramente aiuterà in termini di utilizzo della memoria e allocazioni.
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()
}
Nota che dopo aver creato un nuovo strings.Buidler
, abbiamo chiamato il suo Builder.Grow()
metodo, assicurandoci che alloca una porzione interna abbastanza grande (per evitare riallocazioni quando aggiungiamo le lettere casuali).
strings.Builder
con pacchettounsafe
strings.Builder
costruisce la stringa in un interno []byte
, lo stesso di noi stessi. Quindi fondamentalmente farlo tramite un strings.Builder
ha un certo sovraccarico, l'unica cosa a cui siamo passati strings.Builder
è evitare la copia finale della porzione.
strings.Builder
evita la copia finale usando il pacchetto unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Il fatto è che possiamo farlo anche noi stessi. Quindi l'idea qui è di tornare a costruire la stringa casuale in a []byte
, ma quando abbiamo finito, non convertirla in string
per restituirla, ma fai una conversione non sicura: ottieni una string
che punta alla nostra porzione di byte come dati della stringa .
Ecco come si può fare:
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 ha aggiunto una rand.Read()
funzione e un Rand.Read()
metodo. Dovremmo essere tentati di usarli per leggere tutti i byte necessari in un solo passaggio, al fine di ottenere prestazioni migliori.
C'è un piccolo "problema" con questo: di quanti byte abbiamo bisogno? Potremmo dire: fino al numero di lettere di output. Pensiamo che questa sia una stima superiore, poiché un indice di lettere utilizza meno di 8 bit (1 byte). Ma a questo punto stiamo già facendo di peggio (poiché ottenere i bit casuali è la "parte difficile") e stiamo ottenendo più del necessario.
Si noti inoltre che per mantenere una distribuzione equa di tutti gli indici di lettere, potrebbero esserci alcuni dati casuali "spazzatura" che non saremo in grado di utilizzare, quindi finiremmo per saltare alcuni dati, e quindi finiremmo brevemente quando esamineremo tutti la porzione di byte. Dovremmo ottenere ulteriori byte casuali, "ricorsivamente". E ora stiamo persino perdendo il vantaggio di "chiamata singola per rand
pacchetto" ...
Potremmo "in qualche modo" ottimizzare l'utilizzo dei dati casuali da cui acquisiamo math.Rand()
. Potremmo stimare quanti byte (bit) avremo bisogno. 1 lettera richiede letterIdxBits
bit e abbiamo bisogno di n
lettere, quindi abbiamo bisogno di n * letterIdxBits / 8.0
arrotondare i byte. Siamo in grado di calcolare la probabilità che un indice casuale non sia utilizzabile (vedi sopra), quindi potremmo richiedere di più che "più probabilmente" sarà sufficiente (se si scopre che non lo è, ripetiamo il processo). Possiamo elaborare la porzione di byte come un "flusso di bit" per esempio, per il quale abbiamo una bella libreria di terze parti: github.com/icza/bitio
(divulgazione: sono l'autore).
Ma il codice benchmark mostra ancora che non stiamo vincendo. Perché è così?
La risposta all'ultima domanda è perché rand.Read()
utilizza un ciclo e continua a chiamare Source.Int63()
fino a riempire la sezione passata. Esattamente quello che fa la RandStringBytesMaskImprSrc()
soluzione, senza il buffer intermedio e senza la complessità aggiunta. Ecco perché RandStringBytesMaskImprSrc()
rimane sul trono. Sì, RandStringBytesMaskImprSrc()
utilizza un non sincronizzato a rand.Source
differenza rand.Read()
. Ma il ragionamento si applica ancora; e ciò è dimostrato se usiamo Rand.Read()
invece di rand.Read()
(anche il primo non è sincronizzato).
Va bene, è tempo di confrontare le diverse soluzioni.
Momento della verità:
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
Passando da rune a byte, abbiamo immediatamente un aumento delle prestazioni del 24% e il requisito di memoria scende a un terzo .
Sbarazzarsi rand.Intn()
e usare rand.Int63()
invece dà un altro 20% di spinta.
Il mascheramento (e la ripetizione in caso di grandi indici) rallenta leggermente (a causa delle chiamate ripetute): -22% ...
Ma quando utilizziamo tutti (o la maggior parte) dei 63 bit casuali (10 indici da una rand.Int63()
chiamata): questo accelera alla grande: 3 volte .
Se ci accontentiamo di un (non predefinito, nuovo) rand.Source
invece di rand.Rand
, guadagniamo di nuovo il 21%.
Se lo utilizziamo strings.Builder
, otteniamo un piccolo 3,5% in velocità , ma abbiamo anche ottenuto una riduzione del 50% nell'utilizzo della memoria e nelle allocazioni! Bello!
Infine, se osiamo usare il pacchetto unsafe
invece di strings.Builder
, otteniamo di nuovo un bel 14% .
Confrontando la soluzione finale con la soluzione iniziale: RandStringBytesMaskImprSrcUnsafe()
è 6,3 volte più veloce di RandStringRunes()
, utilizza una sesta memoria e metà delle allocazioni . Missione compiuta.
rand.Source
viene utilizzato un condiviso . Una soluzione alternativa sarebbe quella di passare rand.Source
a alla RandStringBytesMaskImprSrc()
funzione, e in questo modo non è richiesto alcun blocco e quindi le prestazioni / efficienza non vengono effettuate. Ogni goroutine potrebbe avere il suo Source
.
defer
quando è ovvio che non ne hai bisogno. Vedi grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
sbloccare un mutex immediatamente prima o dopo aver chiamato un lucchetto, l'IMO è per lo più un'ottima idea; hai la garanzia di non dimenticare di sbloccare ma anche di sbloccare anche in una funzione intermedia di panico non fatale.
Puoi semplicemente scrivere il codice per questo. Questo codice può essere un po 'più semplice se si desidera fare affidamento sul fatto che tutte le lettere sono byte singoli quando sono codificati in 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())
oppurerand.Seed(time.Now().UnixNano())
math/rand
; usa invece crypto/rand
(come l'opzione 1 di @ Not_A_Golfer).
Due possibili opzioni (ci potrebbero essere più ovviamente):
È possibile utilizzare il crypto/rand
pacchetto che supporta la lettura di array di byte casuali (da / dev / urandom) ed è orientato alla generazione casuale crittografica. vedi http://golang.org/pkg/crypto/rand/#example_Read . Potrebbe essere più lento della normale generazione di numeri pseudo-casuali.
Prendi un numero casuale e cancellalo usando md5 o qualcosa del genere.
Seguendo icza's
una soluzione meravigliosamente spiegata, ecco una modifica che utilizza crypto/rand
invece di 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 desideri una soluzione più generica, che ti consenta di passare la porzione di byte di caratteri per creare la stringa da, puoi provare a usare questo:
// 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 vuoi passare alla tua fonte di casualità, sarebbe banale modificare quanto sopra per accettare un io.Reader
invece di usare crypto/rand
.
Se vuoi numeri casuali crittograficamente sicuri e l'esatto set di caratteri è flessibile (diciamo, base64 va bene), puoi calcolare esattamente quale lunghezza di caratteri casuali hai bisogno dalla dimensione di output desiderata.
Il testo Base 64 è 1/3 più lungo della base 256. (Rapporto 2 ^ 8 vs 2 ^ 6; 8 bit / 6 bit = 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: puoi anche usare RawStdEncoding se preferisci + e / caratteri a - e _
Se si desidera esadecimale, la base 16 è 2x più lunga della base 256. (Rapporto 2 ^ 8 vs 2 ^ 4; 8 bit / 4 bit = 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
}
Tuttavia, è possibile estenderlo a qualsiasi set di caratteri arbitrario se si dispone di un codificatore da base256 a baseN per il proprio set di caratteri. Puoi fare lo stesso calcolo delle dimensioni con quanti bit sono necessari per rappresentare il tuo set di caratteri. Il calcolo del rapporto per qualsiasi set di caratteri arbitrario è:) ratio = 8 / log2(len(charset))
.
Sebbene entrambe queste soluzioni siano sicure, semplici, dovrebbero essere veloci e non sprecare il tuo pool di entropia cripto.
Ecco il parco giochi che mostra che funziona per qualsiasi dimensione. 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 sei disposto ad aggiungere alcuni caratteri al tuo pool di caratteri consentiti, puoi far funzionare il codice con qualsiasi cosa che fornisca byte casuali tramite un io.Reader. Qui stiamo 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
necessario?
len(encodeURL) == 64
. Se random % 64
non è stato fatto, randomPos
potrebbe essere> = 64 e causare un panico fuori limite durante l'esecuzione.
/*
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 alloc /
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 alloc / op