¿Cómo generar una cadena aleatoria de una longitud fija en Go?
300
Quiero una cadena aleatoria de caracteres solamente (mayúsculas o minúsculas), sin números, en Ir. ¿Cuál es la forma más rápida y sencilla de hacer esto?
@VinceEmigh: Aquí hay un meta tema que discute preguntas básicas. meta.stackoverflow.com/q/274645/395461 Personalmente, creo que las preguntas básicas están bien si están bien escritas y son sobre el tema. Mira las respuestas a continuación, ilustran un montón de cosas que serían útiles para alguien nuevo. Para bucles, tipo casting, make (), etc.
Shannon Matthews
2
@Shannon " Esta pregunta no muestra ningún esfuerzo de investigación " (primera respuesta altamente votada en su enlace) - A eso me refería. No muestra ningún esfuerzo de investigación. Ningún esfuerzo (un intento, o incluso afirmar que buscó en línea, lo que obviamente no ha hecho). Aunque sería útil para alguien nuevo , este sitio no está enfocado en enseñar nuevas personas. Se centra en responder problemas / preguntas de programación específicos, no en tutoriales / guías. Aunque podría usarse para este último, ese no es el enfoque y, por lo tanto, esta pregunta debería cerrarse. En cambio, su cuchara /:
Vince Emigh
99
@VinceEmigh Hice esta pregunta hace un año. Había buscado en línea cadenas al azar y también había leído documentos. Pero no fue útil. Si no he escrito en la pregunta, no significa que no haya investigado.
La pregunta pide "la forma más rápida y sencilla" . Abordemos también la parte más rápida . Llegaremos a nuestro código final y más rápido de manera iterativa. La evaluación comparativa de cada iteración se puede encontrar al final de la respuesta.
Todas las soluciones y el código de evaluación comparativa se pueden encontrar en Go Playground . El código en Playground es un archivo de prueba, no un ejecutable. Tienes que guardarlo en un archivo llamado XX_test.goy ejecutarlo con
go test -bench .-benchmem
Prólogo :
La solución más rápida no es una solución de acceso si solo necesita una cadena aleatoria. Para eso, la solución de Paul es perfecta. Esto es si el rendimiento sí importa. Aunque los primeros 2 pasos ( Bytes y resto ) pueden ser un compromiso aceptable: mejoran el rendimiento en un 50% (vea los números exactos en la sección II. Punto de referencia ), y no aumentan la complejidad de manera significativa.
Dicho esto, incluso si no necesita la solución más rápida, leer esta respuesta puede ser aventurero y educativo.
I. Mejoras
1. Génesis (Runas)
Como recordatorio, la solución general original que estamos mejorando es esta:
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. Bytes
Si los caracteres para elegir y ensamblar la cadena aleatoria contienen solo las letras mayúsculas y minúsculas del alfabeto inglés, podemos trabajar con bytes solo porque las letras del alfabeto inglés se asignan a los bytes 1 a 1 en la codificación UTF-8 (que así es como Go almacena cadenas).
Entonces en lugar de:
var letters =[]rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
nosotros podemos usar:
var letters =[]bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Ahora esto ya es una gran mejora: podríamos lograr que sea un const(hay stringconstantes pero no hay constantes de corte ). Como ganancia adicional, la expresión len(letters)también será a const! (La expresión len(s)es constante si ses una cadena constante).
¿Y a qué costo? Nada en absoluto. stringSe puede indexar s que indexa sus bytes, perfecto, exactamente lo que queremos.
Nuestro próximo destino se ve así:
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. Resto
Las soluciones anteriores obtienen un número aleatorio para designar una letra aleatoria llamando a rand.Intn()qué delegados a Rand.Intn()qué delegados Rand.Int31n().
Esto es mucho más lento en comparación con el rand.Int63()que produce un número aleatorio con 63 bits aleatorios.
Entonces podríamos simplemente llamar rand.Int63()y usar el resto después de dividir por len(letterBytes):
func RandStringBytesRmndr(n int)string{
b := make([]byte, n)for i := range b {
b[i]= letterBytes[rand.Int63()% int64(len(letterBytes))]}returnstring(b)}
Esto funciona y es significativamente más rápido, la desventaja es que la probabilidad de todas las letras no será exactamente la misma (suponiendo que rand.Int63()produzca todos los números de 63 bits con la misma probabilidad). Aunque la distorsión es extremadamente pequeña ya que el número de letras 52es mucho más pequeño que 1<<63 - 1, por lo tanto, en la práctica esto está perfectamente bien.
Para que esto sea más fácil de entender: digamos que desea un número aleatorio en el rango de 0..5. Usando 3 bits aleatorios, esto produciría los números 0..1con doble probabilidad que desde el rango 2..5. Usando 5 bits aleatorios, los números dentro del rango 0..1ocurrirían con 6/32probabilidad y los números dentro del rango 2..5con 5/32probabilidad que ahora está más cerca del deseado. Aumentar el número de bits hace que esto sea menos significativo, cuando alcanza 63 bits, es insignificante.
4. Enmascaramiento
Sobre la base de la solución anterior, podemos mantener la distribución equitativa de las letras utilizando solo la mayor cantidad posible de bits del número aleatorio para representar la cantidad de letras. Así por ejemplo, si tenemos 52 cartas, se requiere 6 bits para representarlo: 52 = 110100b. Entonces solo usaremos los 6 bits más bajos del número devuelto por rand.Int63(). Y para mantener una distribución equitativa de las letras, solo "aceptamos" el número si cae dentro del rango 0..len(letterBytes)-1. Si los bits más bajos son mayores, lo descartamos y consultamos un nuevo número aleatorio.
Tenga en cuenta que la probabilidad de que los bits más bajos sean mayores o iguales len(letterBytes)es menor que 0.5en general ( 0.25en promedio), lo que significa que incluso si este fuera el caso, repetir este caso "raro" disminuye la posibilidad de no encontrar un buen número. Después de la nrepetición, la posibilidad de que todavía no tengamos un buen índice es mucho menor pow(0.5, n), y esto es solo una estimación superior. En el caso de 52 letras, la posibilidad de que los 6 bits más bajos no sean buenos es solo (64-52)/64 = 0.19; lo que significa, por ejemplo, que hay posibilidades de no tener un buen número después de 10 repeticiones 1e-8.
Entonces aquí está la solución:
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. Enmascaramiento mejorado
La solución anterior solo usa los 6 bits más bajos de los 63 bits aleatorios devueltos por rand.Int63(). Esto es un desperdicio ya que obtener los bits aleatorios es la parte más lenta de nuestro algoritmo.
Si tenemos 52 letras, eso significa que 6 bits codifican un índice de letras. Entonces 63 bits aleatorios pueden designar 63/6 = 10diferentes índices de letras. Usemos todos esos 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. Fuente
El Enmascaramiento mejorado es bastante bueno, no hay mucho que podamos mejorar. Podríamos, pero no vale la pena la complejidad.
Ahora busquemos algo más para mejorar. La fuente de los números aleatorios.
Hay un crypto/randpaquete que proporciona una Read(b []byte)función, por lo que podríamos usarla para obtener tantos bytes con una sola llamada como necesitemos. Esto no ayudaría en términos de rendimiento, ya que crypto/randimplementa un generador de números pseudoaleatorios criptográficamente seguro, por lo que es mucho más lento.
Así que ceñámonos al math/randpaquete. El rand.Randutiliza un rand.Sourcecomo la fuente de bits aleatorios. rand.Sourcees una interfaz que especifica un Int63() int64método: exactamente y lo único que necesitábamos y usábamos en nuestra última solución.
Entonces, realmente no necesitamos un rand.Rand(ya sea explícito o global, uno compartido del randpaquete), a rand.Sourcees perfectamente suficiente para nosotros:
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)}
También tenga en cuenta que esta última solución no requiere que inicialice (inicialice) el global Randdel math/randpaquete, ya que no se utiliza (y nuestro rand.Sourcese inicializa / sembró correctamente).
Una cosa más a tener en cuenta aquí: paquete de documentos de math/randestados:
La fuente predeterminada es segura para uso concurrente por múltiples goroutines.
Por lo tanto, la fuente predeterminada es más lenta que la Sourceque se puede obtener rand.NewSource(), porque la fuente predeterminada debe proporcionar seguridad bajo acceso / uso concurrente, mientras rand.NewSource()que no ofrece esto (y, Sourcepor lo tanto, es más probable que la devolución sea más rápida).
7. Utilizando strings.Builder
Todas las soluciones anteriores devuelven un stringcontenido cuyo primero se construye en un segmento ( []runeen Génesis y []byteen soluciones posteriores) y luego se convierte a string. Esta conversión final tiene que hacer una copia del contenido de la porción, porque los stringvalores son inmutables, y si la conversión no hiciera una copia, no se podría garantizar que el contenido de la cadena no se modifique a través de su porción original. Para obtener detalles, consulte ¿Cómo convertir la cadena utf8 a [] byte? y golang: [] byte (cadena) vs [] byte (* cadena) .
Ir 1.10 introducido strings.Builder. strings.Builderun nuevo tipo que se puede utilizar para construir contenido de un stringsimilar al bytes.Buffer. Lo hace internamente usando a []byte, y cuando terminamos, podemos obtener el stringvalor final usando su Builder.String()método. Pero lo bueno es que hace esto sin realizar la copia de la que acabamos de hablar. Se atreve a hacerlo porque el segmento de bytes utilizado para construir el contenido de la cadena no está expuesto, por lo que se garantiza que nadie puede modificarlo de manera involuntaria o maliciosa para alterar la cadena "inmutable" producida.
Entonces, nuestra siguiente idea es no construir la cadena aleatoria en un segmento, sino con la ayuda de a strings.Builder, de modo que una vez que hayamos terminado, podamos obtener y devolver el resultado sin tener que hacer una copia. Esto puede ayudar en términos de velocidad, y definitivamente ayudará en términos de uso de memoria y asignaciones.
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()}
Tenga en cuenta que después de crear un nuevo strings.Buidler, llamamos a su Builder.Grow()método, asegurándonos de que asigne un segmento interno lo suficientemente grande (para evitar reasignaciones a medida que agregamos letras al azar).
8. "Mimicing" strings.Buildercon paqueteunsafe
strings.Builderconstruye la cadena de forma interna []byte, igual que nosotros mismos. Básicamente, hacerlo a través de a strings.Buildertiene algo de sobrecarga, lo único a lo que nos cambiamos strings.Builderes evitar la copia final del segmento.
strings.Builderevita la copia final usando el paquete unsafe:
// String returns the accumulated string.
func (b *Builder)String()string{return*(*string)(unsafe.Pointer(&b.buf))}
La cuestión es que también podemos hacer esto nosotros mismos. Entonces, la idea aquí es volver a construir la cadena aleatoria en a []byte, pero cuando hayamos terminado, no la convierta para stringque regrese, sino que realice una conversión insegura: obtenga una stringque apunte a nuestro segmento de bytes como datos de cadena .
Así es como se puede hacer:
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. Usando rand.Read())
Go 1.7 agregó una rand.Read()función y un Rand.Read()método. Deberíamos tener la tentación de usarlos para leer tantos bytes como sea necesario en un solo paso, a fin de lograr un mejor rendimiento.
Hay un pequeño "problema" con esto: ¿cuántos bytes necesitamos? Podríamos decir: tanto como el número de letras de salida. Pensaríamos que esta es una estimación superior, ya que un índice de letras usa menos de 8 bits (1 byte). Pero en este punto ya estamos peor (ya que obtener los bits aleatorios es la "parte difícil"), y estamos obteniendo más de lo necesario.
También tenga en cuenta que para mantener una distribución equitativa de todos los índices de letras, puede haber algunos datos aleatorios "basura" que no podremos usar, por lo que terminaríamos omitiendo algunos datos y, por lo tanto, terminaremos cortos cuando revisemos todos la rebanada de bytes. Necesitaríamos obtener más bytes aleatorios, "recursivamente". Y ahora incluso estamos perdiendo la randventaja de " llamada única al paquete" ...
Podríamos "algo" optimizar el uso de los datos aleatorios que adquirimos math.Rand(). Podemos estimar cuántos bytes (bits) necesitaremos. 1 letra requiere letterIdxBitsbits, y necesitamos nletras, por lo que necesitamos n * letterIdxBits / 8.0redondear bytes. Podemos calcular la probabilidad de que un índice aleatorio no sea utilizable (ver arriba), por lo que podríamos solicitar más que será "más probable" suficiente (si resulta que no lo es, repetimos el proceso). Podemos procesar el segmento de bytes como un "flujo de bits", por ejemplo, para lo cual tenemos una buena biblioteca de terceros: github.com/icza/bitio(divulgación: soy el autor).
Pero el código de referencia todavía muestra que no estamos ganando. ¿Por que es esto entonces?
La respuesta a la última pregunta es porque rand.Read()usa un bucle y sigue llamando Source.Int63()hasta que llena el segmento pasado. Exactamente lo que hace la RandStringBytesMaskImprSrc()solución, sin el búfer intermedio y sin la complejidad añadida. Por eso RandStringBytesMaskImprSrc()permanece en el trono. Sí, RandStringBytesMaskImprSrc()utiliza un no sincronizado a rand.Sourcediferencia rand.Read(). Pero el razonamiento aún se aplica; y lo cual se prueba si usamos en Rand.Read()lugar de rand.Read()(el primero también no está sincronizado).
II Punto de referencia
Muy bien, es hora de comparar las diferentes soluciones.
Simplemente al cambiar de runas a bytes, inmediatamente tenemos un aumento del rendimiento del 24% , y el requisito de memoria cae a un tercio .
Deshacerse rand.Intn()y usar en su rand.Int63()lugar le da otro impulso del 20% .
El enmascaramiento (y la repetición en caso de grandes índices) se ralentiza un poco (debido a las llamadas de repetición): -22% ...
Pero cuando hacemos uso de todos (o la mayoría) de los 63 bits aleatorios (10 índices de una rand.Int63()llamada): eso acelera a lo grande: 3 veces .
Si nos conformamos con un (no predeterminado, nuevo) en rand.Sourcelugar de rand.Rand, nuevamente ganamos un 21%.
Si utilizamos strings.Builder, ganamos un pequeño 3.5% en velocidad , ¡pero también logramos una reducción del 50% en el uso y las asignaciones de memoria! ¡Eso es bueno!
Finalmente, si nos atrevemos a usar el paquete en unsafelugar de strings.Builder, nuevamente ganamos un buen 14% .
Comparando la solución final con la inicial: RandStringBytesMaskImprSrcUnsafe()es 6.3 veces más rápido que RandStringRunes(), usa una sexta memoria y la mitad de las asignaciones . Misión cumplida.
@RobbieV Sí, porque rand.Sourcese usa un compartido . Una mejor solución sería pasar un rand.Sourcea la RandStringBytesMaskImprSrc()función, y de esa manera no se requiere bloqueo y, por lo tanto, el rendimiento / eficiencia no se ve afectado. Cada gorutina podría tener la suya Source.
icza
113
@icza, esa es una de las mejores respuestas que vi durante mucho tiempo en SO!
@ZanLynx thx para la propina; aunque deferdesbloquear un mutex, ya sea inmediatamente antes o después de llamar a un candado, es sobre todo una buena idea; Tiene la garantía de que no se olvidará de desbloquear, sino que también se desbloqueará incluso en una función media de pánico no fatal.
Mike Atlas
1
@RobbieV parece que este código es seguro para subprocesos / rutinas porque la fuente compartida subyacente ya es un LockedSource que implementa el mutex ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130
Simplemente puede escribir código para ello. Este código puede ser un poco más simple si desea confiar en que todas las letras sean bytes individuales cuando se codifican 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))}
No te olvides del rand.Seed (), de lo contrario, obtienes la misma cadena cada vez que inicias ... rand.Seed (time.Now (). UTC (). UnixNano ())
Evan Lin
2
La adición de Evan es correcta, sin embargo, hay otras opciones similares: rand.Seed(time.Now().Unix())orand.Seed(time.Now().UnixNano())
openwonk
77
Para un secreto difícil de adivinar: una contraseña, una clave criptográfica, etc., nunca use math/rand; use crypto/rand(como la opción 1 de @ Not_A_Golfer) en su lugar.
twotwotwo
1
@EvanLin ¿No será esto adivinable? Si tengo que sembrar el generador, entonces el atacante podría adivinar el tiempo con el que lo estoy sembrando y predecir la misma salida que estoy generando.
Matej
44
Tenga en cuenta que si está probando el programa anterior con seed, en el patio de recreo, obtendrá el mismo resultado todo el tiempo. Lo estaba probando en el patio de recreo y después de un tiempo me di cuenta de esto. Funcionó bien de lo contrario para mí. Espero que ahorre tiempo a alguien :)
Gaurav Sinha
18
Utilice el paquete uniuri , que genera cadenas uniformes (imparciales) criptográficamente seguras.
Descargo de responsabilidad: soy el autor del paquete
Aparte: el autor, dchest, es un excelente desarrollador y ha producido una serie de paquetes pequeños y útiles como este.
Roshambo
16
Dos opciones posibles (puede haber más, por supuesto):
Puede utilizar el crypto/randpaquete que admite la lectura de conjuntos de bytes aleatorios (de / dev / urandom) y está orientado a la generación aleatoria criptográfica. ver http://golang.org/pkg/crypto/rand/#example_Read . Sin embargo, podría ser más lento que la generación normal de números pseudoaleatorios.
Tome un número aleatorio y divídalo con md5 o algo así.
Siguiendo la icza'ssolución maravillosamente explicada, aquí hay una modificación que usa en crypto/randlugar 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 desea una solución más genérica, que le permita pasar la porción de bytes de caracteres para crear la cadena, puede intentar usar esto:
// 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 desea pasar su propia fuente de aleatoriedad, sería trivial modificar lo anterior para aceptar un en io.Readerlugar de usarlo crypto/rand.
Si desea números aleatorios criptográficamente seguros , y el juego de caracteres exacto es flexible (digamos, base64 está bien), puede calcular exactamente cuál es la longitud de caracteres aleatorios que necesita del tamaño de salida deseado.
El texto base 64 es 1/3 más largo que la base 256. (2 ^ 8 vs 2 ^ 6; 8bits / 6bits = relación 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: también puede usar RawStdEncoding si prefiere los caracteres + y / a - y _
Si quiere un hex, la base 16 es 2 veces más larga 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}
Sin embargo, puede extender esto a cualquier conjunto de caracteres arbitrario si tiene un codificador de base256 a baseN para su conjunto de caracteres. Puede hacer el mismo cálculo de tamaño con cuántos bits se necesitan para representar su conjunto de caracteres. El cálculo de la relación para cualquier conjunto de caracteres arbitrario es:) ratio = 8 / log2(len(charset)).
Aunque ambas soluciones son seguras, simples, deberían ser rápidas y no desperdiciar su grupo de criptoentropía.
Vale la pena mencionar que Go Playground siempre devuelve el mismo número aleatorio, por lo que no verá allí diferentes cadenas aleatorias en diferentes ejecuciones de ese código
Aquí está mi manera) Use math rand o crypto rand como lo desee.
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 está dispuesto a agregar algunos caracteres a su grupo de caracteres permitidos, puede hacer que el código funcione con cualquier cosa que proporcione bytes aleatorios a través de un io.Reader. Aquí lo estamos usando crypto/rand.
// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even// distribution.const encodeURL ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"// A helper function create and fill a slice of length n with characters from// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int)[]byte{
output := make([]byte, n)// We will take n bytes, one byte for each character of output.
randomness := make([]byte, n)// read all random
_, err := rand.Read(randomness)if err !=nil{
panic(err)}// fill 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
}
Debido len(encodeURL) == 64. Si random % 64no se hizo, randomPospodría ser> = 64 y provocar un pánico fuera de los límites en tiempo de ejecución.
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))}
¡Hola! Bienvenido a StackOverflow. Aunque agregó un fragmento de código, su respuesta no incluye ningún contexto sobre "cómo funciona" o "por qué se hace así". Además, recuerde que la pregunta se hace en inglés, por lo que sus comentarios también deben estar en inglés.
Respuestas:
La solución de Paul proporciona una solución simple y general.
La pregunta pide "la forma más rápida y sencilla" . Abordemos también la parte más rápida . Llegaremos a nuestro código final y más rápido de manera iterativa. La evaluación comparativa de cada iteración se puede encontrar al final de la respuesta.
Todas las soluciones y el código de evaluación comparativa se pueden encontrar en Go Playground . El código en Playground es un archivo de prueba, no un ejecutable. Tienes que guardarlo en un archivo llamado
XX_test.go
y ejecutarlo conPrólogo :
Dicho esto, incluso si no necesita la solución más rápida, leer esta respuesta puede ser aventurero y educativo.
I. Mejoras
1. Génesis (Runas)
Como recordatorio, la solución general original que estamos mejorando es esta:
2. Bytes
Si los caracteres para elegir y ensamblar la cadena aleatoria contienen solo las letras mayúsculas y minúsculas del alfabeto inglés, podemos trabajar con bytes solo porque las letras del alfabeto inglés se asignan a los bytes 1 a 1 en la codificación UTF-8 (que así es como Go almacena cadenas).
Entonces en lugar de:
nosotros podemos usar:
O mejor:
Ahora esto ya es una gran mejora: podríamos lograr que sea un
const
(haystring
constantes pero no hay constantes de corte ). Como ganancia adicional, la expresiónlen(letters)
también será aconst
! (La expresiónlen(s)
es constante sis
es una cadena constante).¿Y a qué costo? Nada en absoluto.
string
Se puede indexar s que indexa sus bytes, perfecto, exactamente lo que queremos.Nuestro próximo destino se ve así:
3. Resto
Las soluciones anteriores obtienen un número aleatorio para designar una letra aleatoria llamando a
rand.Intn()
qué delegados aRand.Intn()
qué delegadosRand.Int31n()
.Esto es mucho más lento en comparación con el
rand.Int63()
que produce un número aleatorio con 63 bits aleatorios.Entonces podríamos simplemente llamar
rand.Int63()
y usar el resto después de dividir porlen(letterBytes)
:Esto funciona y es significativamente más rápido, la desventaja es que la probabilidad de todas las letras no será exactamente la misma (suponiendo que
rand.Int63()
produzca todos los números de 63 bits con la misma probabilidad). Aunque la distorsión es extremadamente pequeña ya que el número de letras52
es mucho más pequeño que1<<63 - 1
, por lo tanto, en la práctica esto está perfectamente bien.Para que esto sea más fácil de entender: digamos que desea un número aleatorio en el rango de
0..5
. Usando 3 bits aleatorios, esto produciría los números0..1
con doble probabilidad que desde el rango2..5
. Usando 5 bits aleatorios, los números dentro del rango0..1
ocurrirían con6/32
probabilidad y los números dentro del rango2..5
con5/32
probabilidad que ahora está más cerca del deseado. Aumentar el número de bits hace que esto sea menos significativo, cuando alcanza 63 bits, es insignificante.4. Enmascaramiento
Sobre la base de la solución anterior, podemos mantener la distribución equitativa de las letras utilizando solo la mayor cantidad posible de bits del número aleatorio para representar la cantidad de letras. Así por ejemplo, si tenemos 52 cartas, se requiere 6 bits para representarlo:
52 = 110100b
. Entonces solo usaremos los 6 bits más bajos del número devuelto porrand.Int63()
. Y para mantener una distribución equitativa de las letras, solo "aceptamos" el número si cae dentro del rango0..len(letterBytes)-1
. Si los bits más bajos son mayores, lo descartamos y consultamos un nuevo número aleatorio.Tenga en cuenta que la probabilidad de que los bits más bajos sean mayores o iguales
len(letterBytes)
es menor que0.5
en general (0.25
en promedio), lo que significa que incluso si este fuera el caso, repetir este caso "raro" disminuye la posibilidad de no encontrar un buen número. Después de lan
repetición, la posibilidad de que todavía no tengamos un buen índice es mucho menorpow(0.5, n)
, y esto es solo una estimación superior. En el caso de 52 letras, la posibilidad de que los 6 bits más bajos no sean buenos es solo(64-52)/64 = 0.19
; lo que significa, por ejemplo, que hay posibilidades de no tener un buen número después de 10 repeticiones1e-8
.Entonces aquí está la solución:
5. Enmascaramiento mejorado
La solución anterior solo usa los 6 bits más bajos de los 63 bits aleatorios devueltos por
rand.Int63()
. Esto es un desperdicio ya que obtener los bits aleatorios es la parte más lenta de nuestro algoritmo.Si tenemos 52 letras, eso significa que 6 bits codifican un índice de letras. Entonces 63 bits aleatorios pueden designar
63/6 = 10
diferentes índices de letras. Usemos todos esos 10:6. Fuente
El Enmascaramiento mejorado es bastante bueno, no hay mucho que podamos mejorar. Podríamos, pero no vale la pena la complejidad.
Ahora busquemos algo más para mejorar. La fuente de los números aleatorios.
Hay un
crypto/rand
paquete que proporciona unaRead(b []byte)
función, por lo que podríamos usarla para obtener tantos bytes con una sola llamada como necesitemos. Esto no ayudaría en términos de rendimiento, ya quecrypto/rand
implementa un generador de números pseudoaleatorios criptográficamente seguro, por lo que es mucho más lento.Así que ceñámonos al
math/rand
paquete. Elrand.Rand
utiliza unrand.Source
como la fuente de bits aleatorios.rand.Source
es una interfaz que especifica unInt63() int64
método: exactamente y lo único que necesitábamos y usábamos en nuestra última solución.Entonces, realmente no necesitamos un
rand.Rand
(ya sea explícito o global, uno compartido delrand
paquete), arand.Source
es perfectamente suficiente para nosotros:También tenga en cuenta que esta última solución no requiere que inicialice (inicialice) el global
Rand
delmath/rand
paquete, ya que no se utiliza (y nuestrorand.Source
se inicializa / sembró correctamente).Una cosa más a tener en cuenta aquí: paquete de documentos de
math/rand
estados:Por lo tanto, la fuente predeterminada es más lenta que la
Source
que se puede obtenerrand.NewSource()
, porque la fuente predeterminada debe proporcionar seguridad bajo acceso / uso concurrente, mientrasrand.NewSource()
que no ofrece esto (y,Source
por lo tanto, es más probable que la devolución sea más rápida).7. Utilizando
strings.Builder
Todas las soluciones anteriores devuelven un
string
contenido cuyo primero se construye en un segmento ([]rune
en Génesis y[]byte
en soluciones posteriores) y luego se convierte astring
. Esta conversión final tiene que hacer una copia del contenido de la porción, porque losstring
valores son inmutables, y si la conversión no hiciera una copia, no se podría garantizar que el contenido de la cadena no se modifique a través de su porción original. Para obtener detalles, consulte ¿Cómo convertir la cadena utf8 a [] byte? y golang: [] byte (cadena) vs [] byte (* cadena) .Ir 1.10 introducido
strings.Builder
.strings.Builder
un nuevo tipo que se puede utilizar para construir contenido de unstring
similar albytes.Buffer
. Lo hace internamente usando a[]byte
, y cuando terminamos, podemos obtener elstring
valor final usando suBuilder.String()
método. Pero lo bueno es que hace esto sin realizar la copia de la que acabamos de hablar. Se atreve a hacerlo porque el segmento de bytes utilizado para construir el contenido de la cadena no está expuesto, por lo que se garantiza que nadie puede modificarlo de manera involuntaria o maliciosa para alterar la cadena "inmutable" producida.Entonces, nuestra siguiente idea es no construir la cadena aleatoria en un segmento, sino con la ayuda de a
strings.Builder
, de modo que una vez que hayamos terminado, podamos obtener y devolver el resultado sin tener que hacer una copia. Esto puede ayudar en términos de velocidad, y definitivamente ayudará en términos de uso de memoria y asignaciones.Tenga en cuenta que después de crear un nuevo
strings.Buidler
, llamamos a suBuilder.Grow()
método, asegurándonos de que asigne un segmento interno lo suficientemente grande (para evitar reasignaciones a medida que agregamos letras al azar).8. "Mimicing"
strings.Builder
con paqueteunsafe
strings.Builder
construye la cadena de forma interna[]byte
, igual que nosotros mismos. Básicamente, hacerlo a través de astrings.Builder
tiene algo de sobrecarga, lo único a lo que nos cambiamosstrings.Builder
es evitar la copia final del segmento.strings.Builder
evita la copia final usando el paqueteunsafe
:La cuestión es que también podemos hacer esto nosotros mismos. Entonces, la idea aquí es volver a construir la cadena aleatoria en a
[]byte
, pero cuando hayamos terminado, no la convierta parastring
que regrese, sino que realice una conversión insegura: obtenga unastring
que apunte a nuestro segmento de bytes como datos de cadena .Así es como se puede hacer:
(9. Usando
rand.Read()
)Go 1.7 agregó una
rand.Read()
función y unRand.Read()
método. Deberíamos tener la tentación de usarlos para leer tantos bytes como sea necesario en un solo paso, a fin de lograr un mejor rendimiento.Hay un pequeño "problema" con esto: ¿cuántos bytes necesitamos? Podríamos decir: tanto como el número de letras de salida. Pensaríamos que esta es una estimación superior, ya que un índice de letras usa menos de 8 bits (1 byte). Pero en este punto ya estamos peor (ya que obtener los bits aleatorios es la "parte difícil"), y estamos obteniendo más de lo necesario.
También tenga en cuenta que para mantener una distribución equitativa de todos los índices de letras, puede haber algunos datos aleatorios "basura" que no podremos usar, por lo que terminaríamos omitiendo algunos datos y, por lo tanto, terminaremos cortos cuando revisemos todos la rebanada de bytes. Necesitaríamos obtener más bytes aleatorios, "recursivamente". Y ahora incluso estamos perdiendo la
rand
ventaja de " llamada única al paquete" ...Podríamos "algo" optimizar el uso de los datos aleatorios que adquirimos
math.Rand()
. Podemos estimar cuántos bytes (bits) necesitaremos. 1 letra requiereletterIdxBits
bits, y necesitamosn
letras, por lo que necesitamosn * letterIdxBits / 8.0
redondear bytes. Podemos calcular la probabilidad de que un índice aleatorio no sea utilizable (ver arriba), por lo que podríamos solicitar más que será "más probable" suficiente (si resulta que no lo es, repetimos el proceso). Podemos procesar el segmento de bytes como un "flujo de bits", por ejemplo, para lo cual tenemos una buena biblioteca de terceros:github.com/icza/bitio
(divulgación: soy el autor).Pero el código de referencia todavía muestra que no estamos ganando. ¿Por que es esto entonces?
La respuesta a la última pregunta es porque
rand.Read()
usa un bucle y sigue llamandoSource.Int63()
hasta que llena el segmento pasado. Exactamente lo que hace laRandStringBytesMaskImprSrc()
solución, sin el búfer intermedio y sin la complejidad añadida. Por esoRandStringBytesMaskImprSrc()
permanece en el trono. Sí,RandStringBytesMaskImprSrc()
utiliza un no sincronizado arand.Source
diferenciarand.Read()
. Pero el razonamiento aún se aplica; y lo cual se prueba si usamos enRand.Read()
lugar derand.Read()
(el primero también no está sincronizado).II Punto de referencia
Muy bien, es hora de comparar las diferentes soluciones.
Momento de la verdad:
Simplemente al cambiar de runas a bytes, inmediatamente tenemos un aumento del rendimiento del 24% , y el requisito de memoria cae a un tercio .
Deshacerse
rand.Intn()
y usar en surand.Int63()
lugar le da otro impulso del 20% .El enmascaramiento (y la repetición en caso de grandes índices) se ralentiza un poco (debido a las llamadas de repetición): -22% ...
Pero cuando hacemos uso de todos (o la mayoría) de los 63 bits aleatorios (10 índices de una
rand.Int63()
llamada): eso acelera a lo grande: 3 veces .Si nos conformamos con un (no predeterminado, nuevo) en
rand.Source
lugar derand.Rand
, nuevamente ganamos un 21%.Si utilizamos
strings.Builder
, ganamos un pequeño 3.5% en velocidad , ¡pero también logramos una reducción del 50% en el uso y las asignaciones de memoria! ¡Eso es bueno!Finalmente, si nos atrevemos a usar el paquete en
unsafe
lugar destrings.Builder
, nuevamente ganamos un buen 14% .Comparando la solución final con la inicial:
RandStringBytesMaskImprSrcUnsafe()
es 6.3 veces más rápido queRandStringRunes()
, usa una sexta memoria y la mitad de las asignaciones . Misión cumplida.fuente
rand.Source
se usa un compartido . Una mejor solución sería pasar unrand.Source
a laRandStringBytesMaskImprSrc()
función, y de esa manera no se requiere bloqueo y, por lo tanto, el rendimiento / eficiencia no se ve afectado. Cada gorutina podría tener la suyaSource
.defer
cuando sea obvio que no lo necesita. Ver grokbase.com/t/gg/golang-nuts/158zz5p42w/…defer
desbloquear un mutex, ya sea inmediatamente antes o después de llamar a un candado, es sobre todo una buena idea; Tiene la garantía de que no se olvidará de desbloquear, sino que también se desbloqueará incluso en una función media de pánico no fatal.Simplemente puede escribir código para ello. Este código puede ser un poco más simple si desea confiar en que todas las letras sean bytes individuales cuando se codifican en UTF-8.
fuente
rand.Seed(time.Now().Unix())
orand.Seed(time.Now().UnixNano())
math/rand
; usecrypto/rand
(como la opción 1 de @ Not_A_Golfer) en su lugar.Utilice el paquete uniuri , que genera cadenas uniformes (imparciales) criptográficamente seguras.
Descargo de responsabilidad: soy el autor del paquete
fuente
Dos opciones posibles (puede haber más, por supuesto):
Puede utilizar el
crypto/rand
paquete que admite la lectura de conjuntos de bytes aleatorios (de / dev / urandom) y está orientado a la generación aleatoria criptográfica. ver http://golang.org/pkg/crypto/rand/#example_Read . Sin embargo, podría ser más lento que la generación normal de números pseudoaleatorios.Tome un número aleatorio y divídalo con md5 o algo así.
fuente
Siguiendo la
icza's
solución maravillosamente explicada, aquí hay una modificación que usa encrypto/rand
lugar demath/rand
.Si desea una solución más genérica, que le permita pasar la porción de bytes de caracteres para crear la cadena, puede intentar usar esto:
Si desea pasar su propia fuente de aleatoriedad, sería trivial modificar lo anterior para aceptar un en
io.Reader
lugar de usarlocrypto/rand
.fuente
Si desea números aleatorios criptográficamente seguros , y el juego de caracteres exacto es flexible (digamos, base64 está bien), puede calcular exactamente cuál es la longitud de caracteres aleatorios que necesita del tamaño de salida deseado.
El texto base 64 es 1/3 más largo que la base 256. (2 ^ 8 vs 2 ^ 6; 8bits / 6bits = relación 1.333)
Nota: también puede usar RawStdEncoding si prefiere los caracteres + y / a - y _
Si quiere un hex, la base 16 es 2 veces más larga que la base 256. (2 ^ 8 vs 2 ^ 4; 8bits / 4bits = 2x ratio)
Sin embargo, puede extender esto a cualquier conjunto de caracteres arbitrario si tiene un codificador de base256 a baseN para su conjunto de caracteres. Puede hacer el mismo cálculo de tamaño con cuántos bits se necesitan para representar su conjunto de caracteres. El cálculo de la relación para cualquier conjunto de caracteres arbitrario es:)
ratio = 8 / log2(len(charset))
.Aunque ambas soluciones son seguras, simples, deberían ser rápidas y no desperdiciar su grupo de criptoentropía.
Aquí está el patio de juegos que muestra que funciona para cualquier tamaño. https://play.golang.org/p/i61WUVR8_3Z
fuente
fuente
[]byte
?Aquí está mi manera) Use math rand o crypto rand como lo desee.
fuente
Si está dispuesto a agregar algunos caracteres a su grupo de caracteres permitidos, puede hacer que el código funcione con cualquier cosa que proporcione bytes aleatorios a través de un io.Reader. Aquí lo estamos usando
crypto/rand
.fuente
random % 64
necesario?len(encodeURL) == 64
. Sirandom % 64
no se hizo,randomPos
podría ser> = 64 y provocar un pánico fuera de los límites en tiempo de ejecución.24.0 ns / op 16 B / op 1 allocs /
fuente
BenchmarkRandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op
fuente