Я хочу только случайную строку символов (в верхнем или нижнем регистре), без цифр в Go. Какой самый быстрый и простой способ сделать это?
Я хочу только случайную строку символов (в верхнем или нижнем регистре), без цифр в Go. Какой самый быстрый и простой способ сделать это?
Ответы:
Решение Павла дает простое общее решение.
Вопрос просит «самый быстрый и простой способ» . Давайте рассмотрим самую быструю часть. Мы дойдем до финального и самого быстрого кода итеративным способом. Сравнительный анализ каждой итерации можно найти в конце ответа.
Все решения и код тестирования можно найти на Go Playground . Код на игровой площадке - это тестовый файл, а не исполняемый файл. Вы должны сохранить его в файл с именем XX_test.go
и запустить его с
go test -bench . -benchmem
Предисловие :
Самое быстрое решение - это не переход, если вам нужна случайная строка. Для этого идеально подходит решение Павла. Это если производительность имеет значение. Хотя первые 2 шага ( байты и остаток ) могут быть приемлемым компромиссом: они улучшают производительность примерно на 50% (см. Точные цифры в разделе II. Контрольные показатели ) и не увеличивают сложность значительно.
Сказав это, даже если вам не нужно самое быстрое решение, чтение этого ответа может быть авантюрным и образовательным.
Напоминаем, что оригинальное общее решение, которое мы улучшаем, таково:
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)
}
Если символы для выбора и сборки случайной строки содержат только прописные и строчные буквы английского алфавита, мы можем работать с байтами только потому, что буквы английского алфавита отображаются в байты 1-в-1 в кодировке UTF-8 (что как Go хранит строки).
Так что вместо:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
мы можем использовать:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
Или даже лучше:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Теперь это уже большое улучшение: мы могли бы добиться того, чтобы оно было const
(есть string
константы, но нет констант срезов ). В качестве дополнительного усиления, выражение len(letters)
также будет const
! (Выражение len(s)
является константой, если s
является строковой константой.)
И какой ценой? Вообще ничего string
Можно индексировать s, который индексирует свои байты, в точности то, что мы хотим.
Наш следующий пункт назначения выглядит следующим образом:
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)
}
Предыдущие решения получают случайное число для обозначения случайной буквы, вызывая rand.Intn()
делегатов, Rand.Intn()
которым делегаты Rand.Int31n()
.
Это намного медленнее, чем rand.Int63()
случайное число с 63 случайными битами.
Таким образом, мы могли бы просто позвонить rand.Int63()
и использовать остаток после деления на 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)
}
Это работает и работает значительно быстрее, недостатком является то, что вероятность всех букв будет не одинаковой (при условии, что rand.Int63()
все 63-разрядные числа производятся с равной вероятностью). Хотя искажение чрезвычайно мало, так как количество букв 52
намного-намного меньше 1<<63 - 1
, поэтому на практике это совершенно нормально.
Чтобы это было проще понять: допустим, вы хотите случайное число в диапазоне 0..5
. Используя 3 случайных бита, можно получить числа 0..1
с двойной вероятностью, чем из диапазона 2..5
. Используя 5 случайных битов, числа в диапазоне 0..1
будут появляться с 6/32
вероятностью, а числа в диапазоне 2..5
с 5/32
вероятностью, которая теперь ближе к желаемой. Увеличение количества битов делает это менее значимым, при достижении 63 битов оно незначительно.
Основываясь на предыдущем решении, мы можем поддерживать равное распределение букв, используя только столько младших битов случайного числа, сколько требуется для представления количества букв. Так, например , если у нас есть 52 буквы, она требует 6 бит для представления его: 52 = 110100b
. Поэтому мы будем использовать только младшие 6 бит числа, возвращаемого rand.Int63()
. И чтобы обеспечить равномерное распределение букв, мы только «принимаем» число, если оно попадает в диапазон 0..len(letterBytes)-1
. Если младшие биты больше, мы отбрасываем их и запрашиваем новое случайное число.
Обратите внимание, что вероятность того, что младшие биты будут больше или равны, len(letterBytes)
меньше, чем 0.5
в целом ( 0.25
в среднем), что означает, что, даже если бы это было так, повторение этого «редкого» случая уменьшает вероятность не найти хороший число. После n
повторения вероятность того, что у нас все-таки не будет хорошего показателя, будет намного меньше pow(0.5, n)
, и это всего лишь верхняя оценка. В случае 52 букв вероятность того, что 6 младших битов не являются хорошими, является единственной (64-52)/64 = 0.19
; это означает, например, что вероятность не иметь хорошего числа после 10 повторений равна 1e-8
.
Итак, вот решение:
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)
}
Предыдущее решение использует только младшие 6 битов из 63 случайных битов, возвращаемых rand.Int63()
. Это пустая трата, поскольку получение случайных битов - самая медленная часть нашего алгоритма.
Если у нас 52 буквы, это означает, что 6 бит кодируют буквенный индекс. Таким образом, 63 случайных бита могут обозначать 63/6 = 10
разные буквенные индексы. Давайте использовать все эти 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)
}
Маскировка Улучшение довольно хорошо, не так много мы можем улучшить его. Мы могли бы, но не стоит сложности.
Теперь давайте найдем что-то еще для улучшения. Источник случайных чисел.
Существует crypto/rand
пакет, который предоставляет Read(b []byte)
функцию, поэтому мы можем использовать ее для получения столько байтов за один вызов, сколько нам нужно. Это не поможет с точки зрения производительности, поскольку crypto/rand
реализует криптографически безопасный генератор псевдослучайных чисел, поэтому он намного медленнее.
Итак, давайте придерживаться math/rand
пакета. rand.Rand
Использует в rand.Source
качестве источника случайных битов. rand.Source
это интерфейс, который определяет Int63() int64
метод: именно то, что нам нужно и используется в нашем последнем решении.
Так что нам на самом деле не нужен rand.Rand
(явный или глобальный, совместно используемый один из rand
пакетов), rand.Source
нам вполне достаточно:
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)
}
Также обратите внимание , что это последнее решение не требует инициализации (семя) глобальная Rand
часть math/rand
пакета , как не используется (и наша rand.Source
правильно инициализирован / затравку).
Еще одна вещь, которую следует отметить здесь: пакет документов math/rand
состояний:
Источник по умолчанию безопасен для одновременного использования несколькими программами.
Таким образом, источник по умолчанию медленнее, чем тот, Source
который может быть получен rand.NewSource()
, потому что источник по умолчанию должен обеспечивать безопасность при одновременном доступе / использовании, но rand.NewSource()
не предлагает этого (и, следовательно, Source
возвращаемый им более вероятно будет быстрее).
strings.Builder
Все предыдущие решения возвращают string
содержимое, содержимое которого сначала создается в срезе ( []rune
в Genesis и []byte
последующих решениях), а затем преобразуется в string
. Это окончательное преобразование должно сделать копию содержимого среза, поскольку string
значения неизменны, и если преобразование не сделает копию, нельзя гарантировать, что содержимое строки не будет изменено с помощью исходного среза. Подробнее см. Как преобразовать строку utf8 в [] байт? и golang: [] byte (строка) против [] byte (* строка) .
Go 1.10 введен strings.Builder
. strings.Builder
новый тип, который мы можем использовать для создания содержимого, string
аналогичного bytes.Buffer
. Он делает это внутренне, используя a []byte
, и когда мы закончим, мы можем получить окончательное string
значение, используя его Builder.String()
метод. Но что здорово, так это то, что он делает это, не выполняя копию, о которой мы только что говорили. Это осмеливается сделать, потому что фрагмент байта, используемый для создания содержимого строки, не предоставляется, поэтому гарантируется, что никто не сможет непреднамеренно или злонамеренно изменить его, чтобы изменить созданную «неизменяемую» строку.
Поэтому наша следующая идея - не создавать случайную строку в срезе, а с помощью a strings.Builder
, поэтому, как только мы закончим, мы можем получить и вернуть результат, не создавая его копию. Это может помочь с точки зрения скорости, и это определенно поможет с точки зрения использования памяти и распределения.
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()
}
Обратите внимание, что после создания нового strings.Buidler
мы вызвали его Builder.Grow()
метод, чтобы убедиться, что он выделяет достаточно большой внутренний срез (чтобы избежать перераспределения при добавлении случайных букв).
strings.Builder
с пакетомunsafe
strings.Builder
строит строку во внутреннем []byte
, так же, как мы сделали сами. Таким образом, в основном выполнение этого с помощью некоторого strings.Builder
количества издержек, единственное, на что мы переключились, strings.Builder
- это избежать окончательного копирования фрагмента.
strings.Builder
избегает окончательной копии с помощью пакета unsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
Дело в том, что мы тоже можем сделать это сами. Таким образом, идея здесь заключается в том, чтобы вернуться к построению случайной строки в a []byte
, но когда мы закончим, не конвертируем ее string
в возвращаемое значение, а выполняем небезопасное преобразование: получаем a, string
который указывает на наш фрагмент байта в качестве строковых данных. ,
Вот как это можно сделать:
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()
)Перейти 1.7 добавлен в rand.Read()
функцию и Rand.Read()
метод. Мы должны испытать желание использовать их для чтения столько байтов, сколько нам нужно, за один шаг, чтобы добиться лучшей производительности.
В этом есть одна маленькая проблема: сколько байтов нам нужно? Мы могли бы сказать: столько, сколько количество выводимых букв. Мы думаем, что это верхняя оценка, так как буквенный индекс использует менее 8 бит (1 байт). Но в этот момент у нас уже все хуже (поскольку получение случайных битов - это «сложная часть»), и мы получаем больше, чем нужно.
Также обратите внимание, что для обеспечения равномерного распределения всех буквенных индексов могут быть некоторые «мусорные» случайные данные, которые мы не сможем использовать, поэтому мы в конечном итоге пропустим некоторые данные и, таким образом, окажемся короткими, когда пройдем все срез байта. Нам нужно было бы дополнительно получать больше случайных байтов, «рекурсивно». И теперь мы даже теряем rand
преимущество "одного звонка в пакет" ...
Мы могли бы «несколько» оптимизировать использование случайных данных, которые мы получаем math.Rand()
. Мы можем оценить, сколько байтов (битов) нам понадобится. 1 буква требует letterIdxBits
битов, и нам нужны n
буквы, поэтому нам нужно n * letterIdxBits / 8.0
округлить байты. Мы можем рассчитать вероятность того, что случайный индекс не будет использоваться (см. Выше), поэтому мы могли бы запросить больше, что будет «более вероятно», будет достаточно (если окажется, что это не так, мы повторяем процесс). Например, мы можем обработать фрагмент байта как «поток битов», для которого у нас есть хорошая сторонняя библиотека lib: github.com/icza/bitio
(раскрытие: я автор).
Но контрольный код все еще показывает, что мы не выигрываем. Почему это так?
Ответ на последний вопрос заключается в том, что rand.Read()
использует цикл и продолжает вызывать, Source.Int63()
пока не заполнит переданный фрагмент. Именно то, что RandStringBytesMaskImprSrc()
делает решение, без промежуточного буфера и без дополнительной сложности. Вот почему RandStringBytesMaskImprSrc()
остается на троне. Да, RandStringBytesMaskImprSrc()
использует несинхронизированный в rand.Source
отличие от rand.Read()
. Но рассуждение все еще применяется; и что доказано, если мы используем Rand.Read()
вместо rand.Read()
(первый также не синхронизирован).
Хорошо, пришло время для сравнения различных решений.
Момент истины:
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
Просто переключаясь с рун на байты, мы сразу получаем прирост производительности на 24% , а потребность в памяти снижается до одной трети .
Избавление rand.Intn()
и использование rand.Int63()
вместо этого дает еще 20% прироста.
Маскировка (и повторение в случае больших индексов) немного замедляется (из-за повторяющихся вызовов): -22% ...
Но когда мы используем все (или большинство) из 63 случайных битов (10 индексов от одного rand.Int63()
вызова): это значительно ускоряется: в 3 раза .
Если мы согласимся с (не по умолчанию, новый) rand.Source
вместо rand.Rand
, мы снова получим 21%.
Если мы используем strings.Builder
, мы получаем крошечные 3,5% в скорости , но мы также достигли 50% сокращения использования памяти и распределения! Это мило!
Наконец, если мы решимся использовать пакет unsafe
вместо strings.Builder
, мы снова получим хорошие 14% .
Сравнивая финал исходного раствора: RandStringBytesMaskImprSrcUnsafe()
в 6,3 раза быстрее , чем RandStringRunes()
, использует одну шестую памяти и половину , как несколько распределений . Миссия выполнена.
rand.Source
. Лучшим обходным решением было бы передать a rand.Source
в RandStringBytesMaskImprSrc()
функцию, и таким образом блокировка не требуется, и поэтому производительность / эффективность не влияют. У каждой рутины может быть свое Source
.
defer
когда очевидно, что вам это не нужно. См. Grokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
разблокировать мьютекс непосредственно перед или после вызова блокировки - это, по большей части, очень хорошая идея; Вы оба гарантированно не забудете разблокировать, но и разблокировать даже в середине фатальной функции без фатальной паники.
Вы можете просто написать код для этого. Этот код может быть немного проще, если вы хотите, чтобы при кодировании в 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())
илиrand.Seed(time.Now().UnixNano())
math/rand
; используйте crypto/rand
взамен (например, вариант 1 Not_A_Golfer).
Два возможных варианта (может быть больше, конечно):
Вы можете использовать crypto/rand
пакет, который поддерживает чтение случайных байтовых массивов (из / dev / urandom) и ориентирован на криптографическую генерацию случайных чисел. см. http://golang.org/pkg/crypto/rand/#example_Read . Хотя это может быть медленнее, чем обычная генерация псевдослучайных чисел.
Возьмите случайное число и хэшируйте его, используя md5 или что-то вроде этого.
Следуя icza's
чудесно объясненному решению, вот его модификация, которая использует crypto/rand
вместо 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
}
Если вам нужно более общее решение, которое позволяет передавать фрагмент символьных байтов для создания строки из, вы можете попробовать использовать это:
// 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)
}
Если вы хотите передать свой собственный источник случайности, было бы тривиально изменить вышеупомянутое, чтобы принять io.Reader
вместо использования crypto/rand
.
Если вам нужны криптографически безопасные случайные числа, а точная кодировка является гибкой (скажем, с base64 все в порядке), вы можете точно рассчитать, какая длина случайных символов вам нужна, исходя из желаемого выходного размера.
Текст Base 64 на 1/3 длиннее, чем Base 256. (2 ^ 8 против 2 ^ 6; соотношение 8 бит / 6 бит = 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
}
Примечание: вы также можете использовать RawStdEncoding, если предпочитаете символы + и / вместо - и _
Если вы хотите гекс, база 16 в 2 раза длиннее, чем база 256. (2 ^ 8 против 2 ^ 4; 8 бит / 4 бит = 2х соотношение)
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
}
Однако вы можете расширить это на любой произвольный набор символов, если у вас есть кодировщик base256 - baseN для вашего набора символов. Вы можете сделать то же самое вычисление размера, сколько битов необходимо для представления вашего набора символов. Расчет коэффициента для любой произвольной кодировки:) ratio = 8 / log2(len(charset))
.
Хотя оба эти решения безопасны, просты, должны быть быстрыми и не тратить ваш криптоэнтропийный пул.
Вот детская площадка, показывающая, что она работает для любого размера. 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
?
Если вы хотите добавить несколько символов в свой пул разрешенных символов, вы можете заставить код работать с любым, который предоставляет случайные байты через io.Reader. Здесь мы используем 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
нужно?
len(encodeURL) == 64
. Если этого random % 64
не сделать, это randomPos
может быть> = 64 и вызвать панику вне границ во время выполнения.
/*
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 нс / оп 16 B / оп 1 распределяет /
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 нс / оп 16 B / оп 1 выделяет / оп