Je veux une chaîne aléatoire de caractères uniquement (majuscules ou minuscules), pas de chiffres, dans Go. Quelle est la manière la plus rapide et la plus simple de procéder?
Je veux une chaîne aléatoire de caractères uniquement (majuscules ou minuscules), pas de chiffres, dans Go. Quelle est la manière la plus rapide et la plus simple de procéder?
Réponses:
La solution de Paul fournit une solution simple et générale.
La question demande "le moyen le plus rapide et le plus simple" . Parlons le plus rapidement partie . Nous arriverons à notre code final le plus rapide de manière itérative. L'analyse comparative de chaque itération se trouve à la fin de la réponse.
Toutes les solutions et le code de référence peuvent être trouvés sur le Go Playground . Le code sur le Playground est un fichier de test, pas un exécutable. Vous devez l'enregistrer dans un fichier nommé XX_test.go
et l'exécuter avec
go test -bench . -benchmem
Avant - propos :
La solution la plus rapide n'est pas une solution idéale si vous avez juste besoin d'une chaîne aléatoire. Pour cela, la solution de Paul est parfaite. C'est si les performances comptent. Bien que les 2 premières étapes ( octets et reste ) puissent être un compromis acceptable: elles améliorent les performances de 50% (voir les chiffres exacts dans la section II. Benchmark ) et n'augmentent pas la complexité de manière significative.
Cela dit, même si vous n'avez pas besoin de la solution la plus rapide, la lecture de cette réponse peut être aventureuse et éducative.
Pour rappel, la solution générale originale que nous améliorons est la suivante:
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)
}
Si les caractères à choisir et à assembler la chaîne aléatoire contiennent uniquement les lettres majuscules et minuscules de l'alphabet anglais, nous ne pouvons travailler avec des octets que parce que les lettres de l'alphabet anglais sont mappées aux octets 1 à 1 dans l'encodage UTF-8 (qui est la façon dont Go stocke les chaînes).
Donc au lieu de:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
on peut utiliser:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Ou encore mieux:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Maintenant, c'est déjà une grande amélioration: nous pourrions y parvenir const
(il y a des string
constantes mais pas de constantes de tranche ). Comme gain supplémentaire, l'expression len(letters)
sera également un const
! (L'expression len(s)
est constante si s
est une constante chaîne.)
Et à quel prix? Rien du tout. string
s peut être indexé qui indexe ses octets, parfait, exactement ce que nous voulons.
Notre prochaine destination ressemble à ceci:
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)
}
Les solutions précédentes obtiennent un nombre aléatoire pour désigner une lettre aléatoire en appelant les rand.Intn()
délégués Rand.Intn()
auxquels les délégués àRand.Int31n()
.
Ceci est beaucoup plus lent que celui rand.Int63()
qui produit un nombre aléatoire avec 63 bits aléatoires.
Ainsi, nous pourrions simplement appeler rand.Int63()
et utiliser le reste après la division par 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)
}
Cela fonctionne et est beaucoup plus rapide, l'inconvénient est que la probabilité de toutes les lettres ne sera pas exactement la même (en supposant que rand.Int63()
tous les nombres de 63 bits soient identiques ). Bien que la distorsion soit extrêmement faible car le nombre de lettres 52
est beaucoup plus petit que 1<<63 - 1
, donc en pratique, c'est parfaitement bien.
Pour faciliter la compréhension: disons que vous voulez un nombre aléatoire dans la plage de 0..5
. En utilisant 3 bits aléatoires, cela produirait les nombres 0..1
avec une probabilité double par rapport à la plage 2..5
. En utilisant 5 bits aléatoires, les nombres dans la plage 0..1
se produiraient avec 6/32
probabilité et les nombres dans la plage 2..5
avec5/32
probabilité qui est maintenant plus proche de la valeur souhaitée. L'augmentation du nombre de bits la rend moins significative, lorsqu'elle atteint 63 bits, elle est négligeable.
En s'appuyant sur la solution précédente, nous pouvons maintenir la distribution égale des lettres en utilisant uniquement autant de bits les plus bas du nombre aléatoire que nécessaire pour représenter le nombre de lettres. Ainsi , par exemple , si nous avons 52 lettres, il faut 6 bits pour le représenter: 52 = 110100b
. Nous n'utiliserons donc que les 6 bits les plus bas du nombre renvoyé par rand.Int63()
. Et pour maintenir une distribution égale des lettres, nous n'acceptons le nombre que s'il tombe dans la plage 0..len(letterBytes)-1
. Si les bits les plus bas sont supérieurs, nous les éliminons et demandons un nouveau nombre aléatoire.
Notez que la probabilité que les bits les plus bas soient supérieurs ou égaux à len(letterBytes)
est inférieure à celle 0.5
en général ( 0.25
en moyenne), ce qui signifie que même si tel est le cas, la répétition de ce cas "rare" diminue les chances de ne pas trouver un bon nombre. Après la n
répétition, la chance que nous n'ayons pas un bon indice est bien inférieure à pow(0.5, n)
, et ce n'est qu'une estimation supérieure. Dans le cas de 52 lettres, la chance que les 6 bits les plus bas ne soient pas bons est seulement (64-52)/64 = 0.19
; ce qui signifie par exemple que les chances de ne pas avoir un bon nombre après 10 répétitions sont 1e-8
.
Voici donc la solution:
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 solution précédente utilise uniquement les 6 bits les plus bas des 63 bits aléatoires renvoyés par rand.Int63()
. C'est un gaspillage car obtenir les bits aléatoires est la partie la plus lente de notre algorithme.
Si nous avons 52 lettres, cela signifie que 6 bits codent un index de lettres. Ainsi, 63 bits aléatoires peuvent désigner 63/6 = 10
différents indices de lettres. Utilisons tous ces 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)
}
Le masquage amélioré est assez bon, nous ne pouvons pas améliorer grand-chose. Nous pourrions, mais ne valons pas la complexité.
Maintenant, trouvons autre chose à améliorer. La source des nombres aléatoires.
Il y a un crypto/rand
paquet qui fournit une Read(b []byte)
fonction, donc nous pourrions l'utiliser pour obtenir autant d'octets avec un seul appel que nous en avons besoin. Cela n'aiderait pas en termes de performances, car il crypto/rand
implémente un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé, il est donc beaucoup plus lent.
Restons donc sur le math/rand
paquet. Le rand.Rand
utilise un rand.Source
comme source de bits aléatoires. rand.Source
est une interface qui spécifie une Int63() int64
méthode: exactement et la seule chose dont nous avions besoin et utilisée dans notre dernière solution.
Nous n'avons donc pas vraiment besoin d'un rand.Rand
(explicite ou global, partagé d'un rand
package), un rand.Source
est parfaitement suffisant pour nous:
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)
}
Notez également que cette dernière solution ne vous oblige pas à initialiser (amorcer) le global Rand
du math/rand
paquet car il n'est pas utilisé (et notre rand.Source
est correctement initialisé / amorcé).
Encore une chose à noter ici: package doc des math/rand
états:
La source par défaut est sûre pour une utilisation simultanée par plusieurs goroutines.
Ainsi, la source par défaut est plus lente que Source
celle qui peut être obtenue par rand.NewSource()
, car la source par défaut doit fournir une sécurité sous accès / utilisation simultanés, tout en rand.NewSource()
ne l'offrant pas (et donc le Source
retour par elle est plus susceptible d'être plus rapide).
strings.Builder
Toutes les solutions précédentes renvoient un string
dont le contenu est d'abord construit dans une tranche ( []rune
dans Genesis et []byte
dans les solutions suivantes), puis converti en string
. Cette conversion finale doit faire une copie du contenu de la tranche, car les string
valeurs sont immuables, et si la conversion ne faisait pas de copie, il ne pouvait être garanti que le contenu de la chaîne ne serait pas modifié via sa tranche d'origine. Pour plus de détails, voir Comment convertir une chaîne utf8 en [] octet? et golang: [] octet (chaîne) vs [] octet (* chaîne) .
Go 1.10 introduit strings.Builder
. strings.Builder
un nouveau type que nous pouvons utiliser pour créer un contenu string
similaire à bytes.Buffer
. Il le fait en interne en utilisant un []byte
, et lorsque nous avons terminé, nous pouvons obtenir la string
valeur finale en utilisant sonBuilder.String()
méthode. Mais ce qui est cool, c'est qu'il fait cela sans effectuer la copie dont nous venons de parler ci-dessus. Il ose le faire parce que la tranche d'octets utilisée pour construire le contenu de la chaîne n'est pas exposée, il est donc garanti que personne ne peut la modifier involontairement ou par malveillance pour altérer la chaîne "immuable" produite.
Donc, notre prochaine idée est de ne pas construire la chaîne aléatoire dans une tranche, mais avec l'aide de a strings.Builder
, donc une fois que nous avons terminé, nous pouvons obtenir et retourner le résultat sans avoir à en faire une copie. Cela peut aider en termes de vitesse, et cela aidera certainement en termes d'utilisation de la mémoire et d'allocations.
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()
}
Notez qu'après avoir créé une nouvelle strings.Buidler
, nous avons appelé sa Builder.Grow()
méthode, en nous assurant qu'elle alloue une tranche interne suffisamment grande (pour éviter les réallocations lorsque nous ajoutons les lettres aléatoires).
strings.Builder
avec le packageunsafe
strings.Builder
construit la chaîne dans un interne []byte
, comme nous l'avons fait nous-mêmes. Donc, fondamentalement, le faire via un strings.Builder
a une surcharge, la seule chose pour laquelle nous sommes passés strings.Builder
est d'éviter la copie finale de la tranche.
strings.Builder
évite la copie finale en utilisant le package unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Le fait est que nous pouvons aussi le faire nous-mêmes. Donc, l'idée ici est de revenir à la construction de la chaîne aléatoire dans a []byte
, mais lorsque nous avons terminé, ne la convertissez pas string
en retour, mais effectuez une conversion non sécurisée: obtenez un string
qui pointe vers notre tranche d'octets comme données de chaîne .
Voici comment cela peut être fait:
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 a ajouté une rand.Read()
fonction et une Rand.Read()
méthode. Nous devrions être tentés de les utiliser pour lire autant d'octets que nécessaire en une seule étape, afin d'obtenir de meilleures performances.
Il y a un petit "problème" avec ceci: de combien d'octets avons-nous besoin? On pourrait dire: autant que le nombre de lettres de sortie. Nous penserions qu'il s'agit d'une estimation supérieure, car un index de lettres utilise moins de 8 bits (1 octet). Mais à ce stade, nous faisons déjà pire (car obtenir les bits aléatoires est la «partie difficile»), et nous obtenons plus que nécessaire.
Notez également que pour maintenir une distribution égale de tous les indices de lettres, il pourrait y avoir des données aléatoires "poubelles" que nous ne serons pas en mesure d'utiliser, donc nous finirions par sauter certaines données, et donc nous retrouverons court quand nous passerons par tous la tranche d'octets. Nous aurions besoin d'obtenir davantage d'octets aléatoires, "récursivement". Et maintenant, nous perdons même l' rand
avantage du "seul appel au forfait" ...
Nous pourrions "quelque peu" optimiser l'utilisation des données aléatoires que nous acquérons math.Rand()
. Nous pouvons estimer le nombre d'octets (bits) dont nous aurons besoin. 1 lettre nécessite des letterIdxBits
bits, et nous avons besoin de n
lettres, nous avons donc besoin d' n * letterIdxBits / 8.0
arrondir les octets. Nous pouvons calculer la probabilité qu'un indice aléatoire ne soit pas utilisable (voir ci-dessus), nous pourrions donc en demander plus qui sera "plus probable" suffisant (s'il s'avère que ce n'est pas le cas, nous répétons le processus). Nous pouvons traiter la tranche d'octets comme un "flux binaire" par exemple, pour lequel nous avons une belle bibliothèque tierce: github.com/icza/bitio
(divulgation: je suis l'auteur).
Mais le code de référence montre toujours que nous ne gagnons pas. Pourquoi en est-il ainsi?
La réponse à la dernière question est car rand.Read()
utilise une boucle et continue d'appeler Source.Int63()
jusqu'à ce qu'elle remplisse la tranche passée. Exactement ce que fait la RandStringBytesMaskImprSrc()
solution, sans le tampon intermédiaire et sans la complexité supplémentaire. C'est pourquoi RandStringBytesMaskImprSrc()
reste sur le trône. Oui, RandStringBytesMaskImprSrc()
utilise un non synchronisé rand.Source
contrairement rand.Read()
. Mais le raisonnement s'applique toujours; et qui est prouvé si nous utilisons à la Rand.Read()
place de rand.Read()
(le premier est également non synchronisé).
Très bien, il est temps de comparer les différentes solutions.
Moment de vérité:
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
En passant simplement des runes aux octets, nous avons immédiatement un gain de performances de 24% et les besoins en mémoire tombent à un tiers .
Se débarrasser rand.Intn()
et utiliser à la rand.Int63()
place donne encore 20% coup de pouce de .
Le masquage (et la répétition en cas de gros indices) ralentit un peu (à cause des appels de répétition): -22% ...
Mais lorsque nous utilisons tous (ou la plupart) des 63 bits aléatoires (10 indices d'un rand.Int63()
appel): cela accélère beaucoup: 3 fois .
Si nous nous contentons d'un (non-défaut, nouveau) rand.Source
au lieu de rand.Rand
, nous gagnons à nouveau 21%.
Si nous utilisons strings.Builder
, nous gagnons un minuscule 3,5% de vitesse , mais nous avons également obtenu une réduction de 50% de l'utilisation et des allocations de mémoire! C'est zonte!
Enfin si nous osons utiliser le package unsafe
au lieu de strings.Builder
, nous gagnons à nouveau un joli 14% .
Comparer la solution finale à la solution initiale: RandStringBytesMaskImprSrcUnsafe()
est 6,3 fois plus rapide que RandStringRunes()
, utilise un sixième de la mémoire et deux fois moins d'allocations . Mission accomplie.
rand.Source
est utilisé. Une meilleure solution de contournement serait de passer un rand.Source
à la RandStringBytesMaskImprSrc()
fonction, et de cette façon aucun verrouillage n'est requis et donc la performance / efficacité n'est pas effectuée. Chaque goroutine pourrait avoir la sienne Source
.
defer
lorsqu'il est évident que vous n'en avez pas besoin. Voir grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
déverrouiller un mutex immédiatement avant ou après avoir appelé un verrou soit IMO surtout une très bonne idée; vous êtes assuré à la fois de ne pas oublier de déverrouiller mais également de déverrouiller même dans une fonction de panique non fatale.
Vous pouvez simplement écrire du code pour cela. Ce code peut être un peu plus simple si vous souhaitez que les lettres soient toutes des octets uniques lorsqu'elles sont encodées en 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
; utilisez crypto/rand
(comme l'option 1 de @ Not_A_Golfer) à la place.
Deux options possibles (il pourrait y en avoir plus bien sûr):
Vous pouvez utiliser le crypto/rand
package qui prend en charge la lecture de tableaux d'octets aléatoires (à partir de / dev / urandom) et est orienté vers la génération aléatoire cryptographique. voir http://golang.org/pkg/crypto/rand/#example_Read . Cependant, cela pourrait être plus lent que la génération de nombres pseudo-aléatoires normaux.
Prenez un nombre aléatoire et hachez-le en utilisant md5 ou quelque chose comme ça.
Après icza's
une solution merveilleusement expliquée, en voici une modification qui utilise à la crypto/rand
place 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
}
Si vous voulez une solution plus générique, qui vous permet de passer la tranche d'octets de caractères pour créer la chaîne, vous pouvez essayer d'utiliser ceci:
// 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)
}
Si vous voulez transmettre votre propre source d'aléatoire, il serait trivial de modifier ce qui précède pour accepter un io.Reader
au lieu de l'utiliser crypto/rand
.
Si vous voulez des nombres aléatoires sécurisés cryptographiquement et que le jeu de caractères exact est flexible (par exemple, base64 est très bien), vous pouvez calculer exactement la longueur de caractères aléatoires dont vous avez besoin à partir de la taille de sortie souhaitée.
Le texte de la base 64 est 1/3 plus long que la base 256. (2 ^ 8 vs 2 ^ 6; rapport 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
}
Remarque: vous pouvez également utiliser RawStdEncoding si vous préférez les caractères + et / à - et _
Si vous voulez un hex, la base 16 est 2x plus longue que la base 256. (2 ^ 8 vs 2 ^ 4; 8bits / 4bits = 2x ratio)
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
}
Cependant, vous pouvez l'étendre à n'importe quel jeu de caractères arbitraire si vous disposez d'un encodeur base256 à baseN pour votre jeu de caractères. Vous pouvez faire le même calcul de taille avec le nombre de bits nécessaires pour représenter votre jeu de caractères. Le calcul du rapport pour tout jeu de caractères arbitraire est:) ratio = 8 / log2(len(charset))
.
Bien que ces deux solutions soient sécurisées, simples, devraient être rapides et ne gaspillent pas votre pool d'entropie cryptographique.
Voici le terrain de jeu montrant qu'il fonctionne pour n'importe quelle taille. 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
?
Si vous êtes prêt à ajouter quelques caractères à votre pool de caractères autorisés, vous pouvez faire fonctionner le code avec tout ce qui fournit des octets aléatoires via un io.Reader. Ici, nous utilisons 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
nécessaire?
len(encodeURL) == 64
. Si cela random % 64
n'a pas été fait, cela randomPos
pourrait être> = 64 et provoquer une panique hors limites lors de l'exécution.
/*
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 allocs /
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 allocs / op