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?
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?
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 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.
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))]
}
return string(b)
}
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")
O mejor:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Ahora esto ya es una gran mejora: podríamos lograr que sea un const
(hay string
constantes 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 s
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í:
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)
}
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))]
}
return string(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 52
es 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..1
con doble probabilidad que desde el rango 2..5
. Usando 5 bits aleatorios, los números dentro del rango 0..1
ocurrirían con 6/32
probabilidad y los números dentro del rango 2..5
con 5/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.
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.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 la n
repetició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++
}
}
return string(b)
}
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:
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)
}
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 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/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. El rand.Rand
utiliza un rand.Source
como la fuente de bits aleatorios. rand.Source
es una interfaz que especifica un Int63() 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 del rand
paquete), a rand.Source
es 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--
}
return string(b)
}
También tenga en cuenta que esta última solución no requiere que inicialice (inicialice) el global Rand
del math/rand
paquete, ya que no se utiliza (y nuestro rand.Source
se inicializa / sembró correctamente).
Una cosa más a tener en cuenta aquí: paquete de documentos de math/rand
estados:
La fuente predeterminada es segura para uso concurrente por múltiples goroutines.
Por lo tanto, la fuente predeterminada es más lenta que la Source
que se puede obtener rand.NewSource()
, porque la fuente predeterminada debe proporcionar seguridad bajo acceso / uso concurrente, mientras rand.NewSource()
que no ofrece esto (y, Source
por lo tanto, es más probable que la devolución sea más rápida).
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 a string
. Esta conversión final tiene que hacer una copia del contenido de la porción, porque los string
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 un string
similar al bytes.Buffer
. Lo hace internamente usando a []byte
, y cuando terminamos, podemos obtener el string
valor 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).
strings.Builder
con paqueteunsafe
strings.Builder
construye la cadena de forma interna []byte
, igual que nosotros mismos. Básicamente, hacerlo a través de a strings.Builder
tiene algo de sobrecarga, lo único a lo que nos cambiamos strings.Builder
es evitar la copia final del segmento.
strings.Builder
evita 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 string
que regrese, sino que realice una conversión insegura: obtenga una string
que 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))
}
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 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 requiere letterIdxBits
bits, y necesitamos n
letras, por lo que necesitamos n * 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 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.Source
diferencia 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).
Muy bien, es hora de comparar las diferentes soluciones.
Momento de la verdad:
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
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.Source
lugar 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 unsafe
lugar 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.
rand.Source
se usa un compartido . Una mejor solución sería pasar un rand.Source
a 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
.
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.
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())
orand.Seed(time.Now().UnixNano())
math/rand
; use crypto/rand
(como la opción 1 de @ Not_A_Golfer) en su lugar.
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í.
Siguiendo la icza's
solución maravillosamente explicada, aquí hay una modificación que usa en crypto/rand
lugar 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 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 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 desea pasar su propia fuente de aleatoriedad, sería trivial modificar lo anterior para aceptar un en io.Reader
lugar 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.
Aquí está el patio de juegos que muestra que funciona para cualquier tamaño. 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 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 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
necesario?
len(encodeURL) == 64
. Si random % 64
no se hizo, randomPos
podría ser> = 64 y provocar un pánico fuera de los límites en tiempo de ejecución.
/*
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