@ VinceEmigh: вот мета-тема, в которой обсуждаются основные вопросы. meta.stackoverflow.com/q/274645/395461 Лично я думаю, что основные вопросы в порядке, если они хорошо написаны и относятся к теме. Посмотрите на ответы ниже, они иллюстрируют кучу вещей, которые будут полезны для новичка. Для циклов введите casting, make () и т. Д.
Шеннон Мэтьюз
2
@Shannon " Этот вопрос не показывает каких-либо исследований " (первый высоко оцененный ответ в вашей ссылке) - это то, что я имел в виду. Он не показывает никаких исследований. Никаких усилий (попытка или даже утверждение, что он смотрел онлайн, чего у него явно нет). Хотя это было бы полезно для кого-то нового , этот сайт не ориентирован на обучение новых людей. Он ориентирован на ответы на конкретные проблемы / вопросы программирования, а не на учебники / руководства. Хотя это может быть использовано для последнего, это не главное, и поэтому этот вопрос должен быть закрыт. Вместо этого его лживый /:
Винс Эми
9
@ VinceEmigh Я задал этот вопрос год назад. Я искал в Интернете случайные строки и тоже читал документы. Но это не помогло. Если я не написал в вопросе, то это не значит, что я не исследовал.
Вопрос просит «самый быстрый и простой способ» . Давайте рассмотрим самую быструю часть. Мы дойдем до финального и самого быстрого кода итеративным способом. Сравнительный анализ каждой итерации можно найти в конце ответа.
Все решения и код тестирования можно найти на Go Playground . Код на игровой площадке - это тестовый файл, а не исполняемый файл. Вы должны сохранить его в файл с именем XX_test.goи запустить его с
go test -bench .-benchmem
Предисловие :
Самое быстрое решение - это не переход, если вам нужна случайная строка. Для этого идеально подходит решение Павла. Это если производительность имеет значение. Хотя первые 2 шага ( байты и остаток ) могут быть приемлемым компромиссом: они улучшают производительность примерно на 50% (см. Точные цифры в разделе II. Контрольные показатели ) и не увеличивают сложность значительно.
Сказав это, даже если вам не нужно самое быстрое решение, чтение этого ответа может быть авантюрным и образовательным.
I. Улучшения
1. Бытие (Руны)
Напоминаем, что оригинальное общее решение, которое мы улучшаем, таково:
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. Байты
Если символы для выбора и сборки случайной строки содержат только прописные и строчные буквы английского алфавита, мы можем работать с байтами только потому, что буквы английского алфавита отображаются в байты 1-в-1 в кодировке UTF-8 (что как Go хранит строки).
Так что вместо:
var letters =[]rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
мы можем использовать:
var letters =[]bytes("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))]}returnstring(b)}
3. Остаток
Предыдущие решения получают случайное число для обозначения случайной буквы, вызывая 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))]}returnstring(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 битов оно незначительно.
4. Маскировка
Основываясь на предыдущем решении, мы можем поддерживать равное распределение букв, используя только столько младших битов случайного числа, сколько требуется для представления количества букв. Так, например , если у нас есть 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++}}returnstring(b)}
5. Улучшено маскирование
Предыдущее решение использует только младшие 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--}returnstring(b)}
6. Источник
Маскировка Улучшение довольно хорошо, не так много мы можем улучшить его. Мы могли бы, но не стоит сложности.
Теперь давайте найдем что-то еще для улучшения. Источник случайных чисел.
Существует 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--}returnstring(b)}
Также обратите внимание , что это последнее решение не требует инициализации (семя) глобальная Randчасть math/randпакета , как не используется (и наша rand.Sourceправильно инициализирован / затравку).
Еще одна вещь, которую следует отметить здесь: пакет документов math/randсостояний:
Источник по умолчанию безопасен для одновременного использования несколькими программами.
Таким образом, источник по умолчанию медленнее, чем тот, Sourceкоторый может быть получен rand.NewSource(), потому что источник по умолчанию должен обеспечивать безопасность при одновременном доступе / использовании, но rand.NewSource()не предлагает этого (и, следовательно, Sourceвозвращаемый им более вероятно будет быстрее).
7. Использование 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()метод, чтобы убедиться, что он выделяет достаточно большой внутренний срез (чтобы избежать перераспределения при добавлении случайных букв).
8. «Подражание» 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))}
(9. Использование 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()(первый также не синхронизирован).
II. эталонный тест
Хорошо, пришло время для сравнения различных решений.
Просто переключаясь с рун на байты, мы сразу получаем прирост производительности на 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(), использует одну шестую памяти и половину , как несколько распределений . Миссия выполнена.
@RobbieV Да, потому что используется общий ресурс rand.Source. Лучшим обходным решением было бы передать a rand.Sourceв RandStringBytesMaskImprSrc()функцию, и таким образом блокировка не требуется, и поэтому производительность / эффективность не влияют. У каждой рутины может быть свое Source.
icza
113
@icza, это один из лучших ответов, которые я давно видел на SO!
@ZanLynx спасибо за совет; хотя deferразблокировать мьютекс непосредственно перед или после вызова блокировки - это, по большей части, очень хорошая идея; Вы оба гарантированно не забудете разблокировать, но и разблокировать даже в середине фатальной функции без фатальной паники.
Майк Атлас
1
@RobbieV похоже, что этот код безопасен для потоков / горутингов, поскольку базовый общий источник уже является LockedSource, который реализует мьютекс ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130
Вы можете просто написать код для этого. Этот код может быть немного проще, если вы хотите, чтобы при кодировании в UTF-8 все буквы были единичными байтами.
package mainimport("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))}
Не забывайте о rand.Seed (), иначе вы получите одну и ту же строку при каждом первом запуске ... rand.Seed (time.Now (). UTC (). UnixNano ())
Эван Лин
2
Дополнение Эвана верно, однако есть и другие похожие варианты: rand.Seed(time.Now().Unix())илиrand.Seed(time.Now().UnixNano())
openwonk
7
Для трудно угадываемого секрета - пароль, ключ шифрования и т. Д. - никогда не используйте math/rand; используйте crypto/randвзамен (например, вариант 1 Not_A_Golfer).
twotwotwo
1
@EvanLin Разве это не может быть угадано? Если мне нужно запустить генератор, то злоумышленник может угадать время, когда я его заполняю, и предсказать тот же результат, который я генерирую.
Матей
4
Обратите внимание, что если вы попробуете вышеуказанную программу с seed, на игровой площадке go, она будет возвращать один и тот же результат все время. Я пробовал это на детской площадке и через некоторое время понял это. Это работало нормально в противном случае для меня. Надеюсь, это сэкономит кому-то время :)
Кроме того, автор dchest - отличный разработчик, который выпустил несколько небольших полезных пакетов, подобных этой.
Рошамбо
16
Два возможных варианта (может быть больше, конечно):
Вы можете использовать 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++}}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
}
Если вам нужно более общее решение, которое позволяет передавать фрагмент символьных байтов для создания строки из, вы можете попробовать использовать это:
// 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)}
Если вы хотите передать свой собственный источник случайности, было бы тривиально изменить вышеупомянутое, чтобы принять 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)).
Хотя оба эти решения безопасны, просты, должны быть быстрыми и не тратить ваш криптоэнтропийный пул.
Стоит отметить, что Go Playground всегда возвращает одно и то же случайное число, поэтому вы не увидите там разные случайные строки при разных выполнениях этого кода
Вот мой путь) Используйте математический рэнд или крипто-рэнд, как хотите.
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]}
Если вы хотите добавить несколько символов в свой пул разрешенных символов, вы можете заставить код работать с любым, который предоставляет случайные байты через 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 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
}
Потому что len(encodeURL) == 64. Если этого random % 64не сделать, это randomPosможет быть> = 64 и вызвать панику вне границ во время выполнения.
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))}
Здравствуйте! Добро пожаловать в StackOverflow. Несмотря на то, что вы добавили фрагмент кода, ваш ответ не включает никакого контекста о том, «как это работает» или «почему это так». Также, пожалуйста, помните, что вопрос задается на английском языке, поэтому ваши комментарии должны быть на английском языке.
Ответы:
Решение Павла дает простое общее решение.
Вопрос просит «самый быстрый и простой способ» . Давайте рассмотрим самую быструю часть. Мы дойдем до финального и самого быстрого кода итеративным способом. Сравнительный анализ каждой итерации можно найти в конце ответа.
Все решения и код тестирования можно найти на Go Playground . Код на игровой площадке - это тестовый файл, а не исполняемый файл. Вы должны сохранить его в файл с именем
XX_test.go
и запустить его сПредисловие :
Сказав это, даже если вам не нужно самое быстрое решение, чтение этого ответа может быть авантюрным и образовательным.
I. Улучшения
1. Бытие (Руны)
Напоминаем, что оригинальное общее решение, которое мы улучшаем, таково:
2. Байты
Если символы для выбора и сборки случайной строки содержат только прописные и строчные буквы английского алфавита, мы можем работать с байтами только потому, что буквы английского алфавита отображаются в байты 1-в-1 в кодировке UTF-8 (что как Go хранит строки).
Так что вместо:
мы можем использовать:
Или даже лучше:
Теперь это уже большое улучшение: мы могли бы добиться того, чтобы оно было
const
(естьstring
константы, но нет констант срезов ). В качестве дополнительного усиления, выражениеlen(letters)
также будетconst
! (Выражениеlen(s)
является константой, еслиs
является строковой константой.)И какой ценой? Вообще ничего
string
Можно индексировать s, который индексирует свои байты, в точности то, что мы хотим.Наш следующий пункт назначения выглядит следующим образом:
3. Остаток
Предыдущие решения получают случайное число для обозначения случайной буквы, вызывая
rand.Intn()
делегатов,Rand.Intn()
которым делегатыRand.Int31n()
.Это намного медленнее, чем
rand.Int63()
случайное число с 63 случайными битами.Таким образом, мы могли бы просто позвонить
rand.Int63()
и использовать остаток после деления наlen(letterBytes)
:Это работает и работает значительно быстрее, недостатком является то, что вероятность всех букв будет не одинаковой (при условии, что
rand.Int63()
все 63-разрядные числа производятся с равной вероятностью). Хотя искажение чрезвычайно мало, так как количество букв52
намного-намного меньше1<<63 - 1
, поэтому на практике это совершенно нормально.Чтобы это было проще понять: допустим, вы хотите случайное число в диапазоне
0..5
. Используя 3 случайных бита, можно получить числа0..1
с двойной вероятностью, чем из диапазона2..5
. Используя 5 случайных битов, числа в диапазоне0..1
будут появляться с6/32
вероятностью, а числа в диапазоне2..5
с5/32
вероятностью, которая теперь ближе к желаемой. Увеличение количества битов делает это менее значимым, при достижении 63 битов оно незначительно.4. Маскировка
Основываясь на предыдущем решении, мы можем поддерживать равное распределение букв, используя только столько младших битов случайного числа, сколько требуется для представления количества букв. Так, например , если у нас есть 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
.Итак, вот решение:
5. Улучшено маскирование
Предыдущее решение использует только младшие 6 битов из 63 случайных битов, возвращаемых
rand.Int63()
. Это пустая трата, поскольку получение случайных битов - самая медленная часть нашего алгоритма.Если у нас 52 буквы, это означает, что 6 бит кодируют буквенный индекс. Таким образом, 63 случайных бита могут обозначать
63/6 = 10
разные буквенные индексы. Давайте использовать все эти 10:6. Источник
Маскировка Улучшение довольно хорошо, не так много мы можем улучшить его. Мы могли бы, но не стоит сложности.
Теперь давайте найдем что-то еще для улучшения. Источник случайных чисел.
Существует
crypto/rand
пакет, который предоставляетRead(b []byte)
функцию, поэтому мы можем использовать ее для получения столько байтов за один вызов, сколько нам нужно. Это не поможет с точки зрения производительности, посколькуcrypto/rand
реализует криптографически безопасный генератор псевдослучайных чисел, поэтому он намного медленнее.Итак, давайте придерживаться
math/rand
пакета.rand.Rand
Использует вrand.Source
качестве источника случайных битов.rand.Source
это интерфейс, который определяетInt63() int64
метод: именно то, что нам нужно и используется в нашем последнем решении.Так что нам на самом деле не нужен
rand.Rand
(явный или глобальный, совместно используемый один изrand
пакетов),rand.Source
нам вполне достаточно:Также обратите внимание , что это последнее решение не требует инициализации (семя) глобальная
Rand
частьmath/rand
пакета , как не используется (и нашаrand.Source
правильно инициализирован / затравку).Еще одна вещь, которую следует отметить здесь: пакет документов
math/rand
состояний:Таким образом, источник по умолчанию медленнее, чем тот,
Source
который может быть полученrand.NewSource()
, потому что источник по умолчанию должен обеспечивать безопасность при одновременном доступе / использовании, ноrand.NewSource()
не предлагает этого (и, следовательно,Source
возвращаемый им более вероятно будет быстрее).7. Использование
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
, поэтому, как только мы закончим, мы можем получить и вернуть результат, не создавая его копию. Это может помочь с точки зрения скорости, и это определенно поможет с точки зрения использования памяти и распределения.Обратите внимание, что после создания нового
strings.Buidler
мы вызвали егоBuilder.Grow()
метод, чтобы убедиться, что он выделяет достаточно большой внутренний срез (чтобы избежать перераспределения при добавлении случайных букв).8. «Подражание»
strings.Builder
с пакетомunsafe
strings.Builder
строит строку во внутреннем[]byte
, так же, как мы сделали сами. Таким образом, в основном выполнение этого с помощью некоторогоstrings.Builder
количества издержек, единственное, на что мы переключились,strings.Builder
- это избежать окончательного копирования фрагмента.strings.Builder
избегает окончательной копии с помощью пакетаunsafe
:Дело в том, что мы тоже можем сделать это сами. Таким образом, идея здесь заключается в том, чтобы вернуться к построению случайной строки в a
[]byte
, но когда мы закончим, не конвертируем ееstring
в возвращаемое значение, а выполняем небезопасное преобразование: получаем a,string
который указывает на наш фрагмент байта в качестве строковых данных. ,Вот как это можно сделать:
(9. Использование
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()
(первый также не синхронизирован).II. эталонный тест
Хорошо, пришло время для сравнения различных решений.
Момент истины:
Просто переключаясь с рун на байты, мы сразу получаем прирост производительности на 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
. Лучшим обходным решением было бы передать arand.Source
вRandStringBytesMaskImprSrc()
функцию, и таким образом блокировка не требуется, и поэтому производительность / эффективность не влияют. У каждой рутины может быть своеSource
.defer
когда очевидно, что вам это не нужно. См. Grokbase.com/t/gg/golang-nuts/158zz5p42w/…defer
разблокировать мьютекс непосредственно перед или после вызова блокировки - это, по большей части, очень хорошая идея; Вы оба гарантированно не забудете разблокировать, но и разблокировать даже в середине фатальной функции без фатальной паники.Вы можете просто написать код для этого. Этот код может быть немного проще, если вы хотите, чтобы при кодировании в UTF-8 все буквы были единичными байтами.
источник
rand.Seed(time.Now().Unix())
илиrand.Seed(time.Now().UnixNano())
math/rand
; используйтеcrypto/rand
взамен (например, вариант 1 Not_A_Golfer).Используйте пакет uniuri , который генерирует криптографически безопасные однородные (несмещенные) строки.
Отказ от ответственности: я автор пакета
источник
Два возможных варианта (может быть больше, конечно):
Вы можете использовать
crypto/rand
пакет, который поддерживает чтение случайных байтовых массивов (из / dev / urandom) и ориентирован на криптографическую генерацию случайных чисел. см. http://golang.org/pkg/crypto/rand/#example_Read . Хотя это может быть медленнее, чем обычная генерация псевдослучайных чисел.Возьмите случайное число и хэшируйте его, используя md5 или что-то вроде этого.
источник
Следуя
icza's
чудесно объясненному решению, вот его модификация, которая используетcrypto/rand
вместоmath/rand
.Если вам нужно более общее решение, которое позволяет передавать фрагмент символьных байтов для создания строки из, вы можете попробовать использовать это:
Если вы хотите передать свой собственный источник случайности, было бы тривиально изменить вышеупомянутое, чтобы принять
io.Reader
вместо использованияcrypto/rand
.источник
Если вам нужны криптографически безопасные случайные числа, а точная кодировка является гибкой (скажем, с base64 все в порядке), вы можете точно рассчитать, какая длина случайных символов вам нужна, исходя из желаемого выходного размера.
Текст Base 64 на 1/3 длиннее, чем Base 256. (2 ^ 8 против 2 ^ 6; соотношение 8 бит / 6 бит = 1,333)
Примечание: вы также можете использовать RawStdEncoding, если предпочитаете символы + и / вместо - и _
Если вы хотите гекс, база 16 в 2 раза длиннее, чем база 256. (2 ^ 8 против 2 ^ 4; 8 бит / 4 бит = 2х соотношение)
Однако вы можете расширить это на любой произвольный набор символов, если у вас есть кодировщик base256 - baseN для вашего набора символов. Вы можете сделать то же самое вычисление размера, сколько битов необходимо для представления вашего набора символов. Расчет коэффициента для любой произвольной кодировки:)
ratio = 8 / log2(len(charset))
.Хотя оба эти решения безопасны, просты, должны быть быстрыми и не тратить ваш криптоэнтропийный пул.
Вот детская площадка, показывающая, что она работает для любого размера. https://play.golang.org/p/i61WUVR8_3Z
источник
источник
[]byte
?Вот мой путь) Используйте математический рэнд или крипто-рэнд, как хотите.
источник
Если вы хотите добавить несколько символов в свой пул разрешенных символов, вы можете заставить код работать с любым, который предоставляет случайные байты через io.Reader. Здесь мы используем
crypto/rand
.источник
random % 64
нужно?len(encodeURL) == 64
. Если этогоrandom % 64
не сделать, этоrandomPos
может быть> = 64 и вызвать панику вне границ во время выполнения.24,0 нс / оп 16 B / оп 1 распределяет /
источник
BenchmarkRandStr16-8 20000000 68,1 нс / оп 16 B / оп 1 выделяет / оп
источник