Comment générer une chaîne aléatoire d'une longueur fixe dans Go?
300
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?
@VinceEmigh: Voici un méta-sujet traitant des questions de base. meta.stackoverflow.com/q/274645/395461 Personnellement, je pense que les questions de base sont correctes si elles sont bien écrites et sont sur le sujet. Regardez les réponses ci-dessous, elles illustrent un tas de choses qui pourraient être utiles pour quelqu'un de nouveau. Pour les boucles, tapez casting, make (), etc.
Shannon Matthews
2
@Shannon " Cette question ne montre aucun effort de recherche " (première réponse très appréciée dans votre lien) - C'est à cela que je faisais référence. Il ne montre aucun effort de recherche. Aucun effort du tout (une tentative, ou même affirmer qu'il avait regardé en ligne, ce qu'il n'a évidemment pas fait). Bien qu'il soit utile pour quelqu'un de nouveau , ce site n'est pas axé sur l'enseignement de nouvelles personnes. Il est axé sur la réponse à des problèmes / questions de programmation spécifiques, pas sur des tutoriels / guides. Bien qu'il puisse être utilisé pour ce dernier, ce n'est pas l'objectif, et donc cette question devrait être fermée. Au lieu de cela, son cuillère /:
Vince Emigh
9
@VinceEmigh J'ai posé cette question il y a un an. J'avais recherché en ligne des chaînes aléatoires et lu des documents aussi. Mais ce n'était pas utile. Si je n'ai pas écrit dans la question, cela ne signifie pas que je n'ai pas fait de recherche.
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.goet 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.
I. Améliorations
1. Genesis (Runes)
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))]}returnstring(b)}
2. octets
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")
Maintenant, c'est déjà une grande amélioration: nous pourrions y parvenir const(il y a des stringconstantes 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 sest une constante chaîne.)
Et à quel prix? Rien du tout. strings 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))]}returnstring(b)}
3. Reste
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))]}returnstring(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 52est 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..1avec une probabilité double par rapport à la plage 2..5. En utilisant 5 bits aléatoires, les nombres dans la plage 0..1se produiraient avec 6/32probabilité et les nombres dans la plage 2..5avec5/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.
4. Masquage
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.5en général ( 0.25en 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 nré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++}}returnstring(b)}
5. Masquage amélioré
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 = 10diffé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--}returnstring(b)}
6. Source
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/randpaquet 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/randimplémente un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé, il est donc beaucoup plus lent.
Restons donc sur le math/randpaquet. Le rand.Randutilise un rand.Sourcecomme source de bits aléatoires. rand.Sourceest une interface qui spécifie une Int63() int64mé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 randpackage), un rand.Sourceest 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--}returnstring(b)}
Notez également que cette dernière solution ne vous oblige pas à initialiser (amorcer) le global Randdu math/randpaquet car il n'est pas utilisé (et notre rand.Sourceest 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 Sourcecelle 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 Sourceretour par elle est plus susceptible d'être plus rapide).
7. Utilisation strings.Builder
Toutes les solutions précédentes renvoient un stringdont le contenu est d'abord construit dans une tranche ( []runedans Genesis et []bytedans les solutions suivantes), puis converti en string. Cette conversion finale doit faire une copie du contenu de la tranche, car les stringvaleurs 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.Builderun nouveau type que nous pouvons utiliser pour créer un contenu stringsimilaire à bytes.Buffer. Il le fait en interne en utilisant un []byte, et lorsque nous avons terminé, nous pouvons obtenir la stringvaleur 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).
8. "Imitation" strings.Builderavec le packageunsafe
strings.Builderconstruit la chaîne dans un interne []byte, comme nous l'avons fait nous-mêmes. Donc, fondamentalement, le faire via un strings.Buildera une surcharge, la seule chose pour laquelle nous sommes passés strings.Builderest 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 stringen retour, mais effectuez une conversion non sécurisée: obtenez un stringqui 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))}
(9. Utilisation 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' randavantage 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 letterIdxBitsbits, et nous avons besoin de nlettres, nous avons donc besoin d' n * letterIdxBits / 8.0arrondir 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.Sourcecontrairement 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é).
II. Référence
Très bien, il est temps de comparer les différentes solutions.
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.Sourceau 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 unsafeau 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.
@RobbieV Yup, car un partage rand.Sourceest 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.
icza
113
@icza, c'est l'une des meilleures réponses que j'ai vues depuis longtemps sur SO!
@ZanLynx thx pour la pointe; bien que deferdé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.
Mike Atlas
1
@RobbieV il semble que ce code soit sûr pour les threads / goroutines car la source partagée sous-jacente est déjà une LockedSource qui implémente le mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130
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))]}returnstring(b)}
func main(){
rand.Seed(time.Now().UnixNano())
fmt.Println(randSeq(10))}
N'oubliez pas le rand.Seed (), sinon vous obtenez la même chaîne à chaque premier lancement ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
L'ajout d'Evan est correct, mais il existe d'autres options similaires: rand.Seed(time.Now().Unix())ourand.Seed(time.Now().UnixNano())
openwonk
7
Pour un secret difficile à deviner - un mot de passe, une clé de chiffrement, etc. - ne jamais utiliser math/rand; utilisez crypto/rand(comme l'option 1 de @ Not_A_Golfer) à la place.
twotwotwo
1
@EvanLin Ce ne sera pas devinable? Si je dois amorcer le générateur, l'attaquant pourrait deviner le temps avec lequel je l'ensemencer et prédire la même sortie que je génère.
Matej
4
Notez que si vous essayez le programme ci-dessus avec des graines, sur le terrain de jeu, il retournera le même résultat tout le temps. Je l'essayais sur le terrain de jeu et après un certain temps, j'ai réalisé cela. Sinon, ça a bien fonctionné pour moi. J'espère que cela fera gagner du temps à quelqu'un :)
Gaurav Sinha
18
Utilisez le package uniuri , qui génère des chaînes uniformes (non biaisées) cryptographiquement sécurisées.
À part: l'auteur, dchest, est un excellent développeur et a produit un certain nombre de petits packages utiles comme celui-ci.
Roshambo
16
Deux options possibles (il pourrait y en avoir plus bien sûr):
Vous pouvez utiliser le crypto/randpackage 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'sune solution merveilleusement expliquée, en voici une modification qui utilise à la crypto/randplace 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++}}returnstring(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 bytevar bitMask bytefor 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 sliceif idx :=int(randomBytes[j%length]& bitMask); idx < availableCharLength {
result[i]= availableCharBytes[idx]
i++}}returnstring(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.Readerau 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.
il convient de mentionner que Go Playground renvoie toujours le même nombre aléatoire, donc vous ne verrez pas là-dedans différentes chaînes aléatoires lors des différentes exécutions de ce code
Voici ma façon) Utilisez math rand ou crypto rand comme vous le souhaitez.
func randStr(len int)string{
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)// Base 64 can be longer than lenreturn str[:len]}
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 outputfor 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
}
Parce que len(encodeURL) == 64. Si cela random % 64n'a pas été fait, cela randomPospourrait être> = 64 et provoquer une panique hors limites lors de l'exécution.
0xcaff
-1
/*
korzhao
*/package rand
import(
crand "crypto/rand""math/rand""sync""time""unsafe")// Doesn't share the rand library globally, reducing lock contention
type Randstruct{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))}
salut! Bienvenue dans StackOverflow. Bien que vous ayez ajouté un extrait de code, votre réponse n'inclut aucun contexte sur "comment cela fonctionne" ou "pourquoi c'est ainsi". N'oubliez pas non plus que la question est posée en anglais. Vos commentaires doivent donc également être en anglais.
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 avecAvant - propos :
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.
I. Améliorations
1. Genesis (Runes)
Pour rappel, la solution générale originale que nous améliorons est la suivante:
2. octets
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:
on peut utiliser:
Ou encore mieux:
Maintenant, c'est déjà une grande amélioration: nous pourrions y parvenir
const
(il y a desstring
constantes mais pas de constantes de tranche ). Comme gain supplémentaire, l'expressionlen(letters)
sera également unconst
! (L'expressionlen(s)
est constante sis
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:
3. Reste
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ésRand.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 parlen(letterBytes)
: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 lettres52
est beaucoup plus petit que1<<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 nombres0..1
avec une probabilité double par rapport à la plage2..5
. En utilisant 5 bits aléatoires, les nombres dans la plage0..1
se produiraient avec6/32
probabilité et les nombres dans la plage2..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.4. Masquage
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é parrand.Int63()
. Et pour maintenir une distribution égale des lettres, nous n'acceptons le nombre que s'il tombe dans la plage0..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 à celle0.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 lan
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 sont1e-8
.Voici donc la solution:
5. Masquage amélioré
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:6. Source
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 uneRead(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 ilcrypto/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. Lerand.Rand
utilise unrand.Source
comme source de bits aléatoires.rand.Source
est une interface qui spécifie uneInt63() 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'unrand
package), unrand.Source
est parfaitement suffisant pour nous:Notez également que cette dernière solution ne vous oblige pas à initialiser (amorcer) le global
Rand
dumath/rand
paquet car il n'est pas utilisé (et notrerand.Source
est correctement initialisé / amorcé).Encore une chose à noter ici: package doc des
math/rand
états:Ainsi, la source par défaut est plus lente que
Source
celle qui peut être obtenue parrand.NewSource()
, car la source par défaut doit fournir une sécurité sous accès / utilisation simultanés, tout enrand.NewSource()
ne l'offrant pas (et donc leSource
retour par elle est plus susceptible d'être plus rapide).7. Utilisation
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 enstring
. Cette conversion finale doit faire une copie du contenu de la tranche, car lesstring
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 contenustring
similaire àbytes.Buffer
. Il le fait en interne en utilisant un[]byte
, et lorsque nous avons terminé, nous pouvons obtenir lastring
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.Notez qu'après avoir créé une nouvelle
strings.Buidler
, nous avons appelé saBuilder.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).8. "Imitation"
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 unstrings.Builder
a une surcharge, la seule chose pour laquelle nous sommes passésstrings.Builder
est d'éviter la copie finale de la tranche.strings.Builder
évite la copie finale en utilisant le packageunsafe
: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 passtring
en retour, mais effectuez une conversion non sécurisée: obtenez unstring
qui pointe vers notre tranche d'octets comme données de chaîne .Voici comment cela peut être fait:
(9. Utilisation
rand.Read()
)Go 1.7 a ajouté une
rand.Read()
fonction et uneRand.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 desletterIdxBits
bits, et nous avons besoin den
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'appelerSource.Int63()
jusqu'à ce qu'elle remplisse la tranche passée. Exactement ce que fait laRandStringBytesMaskImprSrc()
solution, sans le tampon intermédiaire et sans la complexité supplémentaire. C'est pourquoiRandStringBytesMaskImprSrc()
reste sur le trône. Oui,RandStringBytesMaskImprSrc()
utilise un non synchronisérand.Source
contrairementrand.Read()
. Mais le raisonnement s'applique toujours; et qui est prouvé si nous utilisons à laRand.Read()
place derand.Read()
(le premier est également non synchronisé).II. Référence
Très bien, il est temps de comparer les différentes solutions.
Moment de vérité:
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 à larand.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 derand.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 destrings.Builder
, nous gagnons à nouveau un joli 14% .Comparer la solution finale à la solution initiale:
RandStringBytesMaskImprSrcUnsafe()
est 6,3 fois plus rapide queRandStringRunes()
, utilise un sixième de la mémoire et deux fois moins d'allocations . Mission accomplie.la source
rand.Source
est utilisé. Une meilleure solution de contournement serait de passer unrand.Source
à laRandStringBytesMaskImprSrc()
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 sienneSource
.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.
la source
rand.Seed(time.Now().Unix())
ourand.Seed(time.Now().UnixNano())
math/rand
; utilisezcrypto/rand
(comme l'option 1 de @ Not_A_Golfer) à la place.Utilisez le package uniuri , qui génère des chaînes uniformes (non biaisées) cryptographiquement sécurisées.
Avertissement: je suis l'auteur du package
la source
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.
la source
Après
icza's
une solution merveilleusement expliquée, en voici une modification qui utilise à lacrypto/rand
place demath/rand
.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:
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'utilisercrypto/rand
.la source
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)
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)
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
la source
la source
[]byte
?Voici ma façon) Utilisez math rand ou crypto rand comme vous le souhaitez.
la source
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
.la source
random % 64
nécessaire?len(encodeURL) == 64
. Si celarandom % 64
n'a pas été fait, celarandomPos
pourrait être> = 64 et provoquer une panique hors limites lors de l'exécution.24,0 ns / op 16 B / op 1 allocs /
la source
BenchmarkRandStr16-8 20000000 68,1 ns / op 16 B / op 1 allocs / op
la source