Как сгенерировать случайную строку фиксированной длины в Go?

300

Я хочу только случайную строку символов (в верхнем или нижнем регистре), без цифр в Go. Какой самый быстрый и простой способ сделать это?

Аниш Шах
источник
2
@ VinceEmigh: вот мета-тема, в которой обсуждаются основные вопросы. meta.stackoverflow.com/q/274645/395461 Лично я думаю, что основные вопросы в порядке, если они хорошо написаны и относятся к теме. Посмотрите на ответы ниже, они иллюстрируют кучу вещей, которые будут полезны для новичка. Для циклов введите casting, make () и т. Д.
Шеннон Мэтьюз
2
@Shannon " Этот вопрос не показывает каких-либо исследований " (первый высоко оцененный ответ в вашей ссылке) - это то, что я имел в виду. Он не показывает никаких исследований. Никаких усилий (попытка или даже утверждение, что он смотрел онлайн, чего у него явно нет). Хотя это было бы полезно для кого-то нового , этот сайт не ориентирован на обучение новых людей. Он ориентирован на ответы на конкретные проблемы / вопросы программирования, а не на учебники / руководства. Хотя это может быть использовано для последнего, это не главное, и поэтому этот вопрос должен быть закрыт. Вместо этого его лживый /:
Винс Эми
9
@ VinceEmigh Я задал этот вопрос год назад. Я искал в Интернете случайные строки и тоже читал документы. Но это не помогло. Если я не написал в вопросе, то это не значит, что я не исследовал.
Аниш Шах

Ответы:

809

Решение Павла дает простое общее решение.

Вопрос просит «самый быстрый и простой способ» . Давайте рассмотрим самую быструю часть. Мы дойдем до финального и самого быстрого кода итеративным способом. Сравнительный анализ каждой итерации можно найти в конце ответа.

Все решения и код тестирования можно найти на 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))]
    }
    return string(b)
}

2. Байты

Если символы для выбора и сборки случайной строки содержат только прописные и строчные буквы английского алфавита, мы можем работать с байтами только потому, что буквы английского алфавита отображаются в байты 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)
}

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))]
    }
    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 битов оно незначительно.

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++
        }
    }
    return string(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--
    }

    return string(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--
    }

    return string(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. эталонный тест

Хорошо, пришло время для сравнения различных решений.

Момент истины:

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(), использует одну шестую памяти и половину , как несколько распределений . Миссия выполнена.

icza
источник
8
@RobbieV Да, потому что используется общий ресурс rand.Source. Лучшим обходным решением было бы передать a rand.Sourceв RandStringBytesMaskImprSrc()функцию, и таким образом блокировка не требуется, и поэтому производительность / эффективность не влияют. У каждой рутины может быть свое Source.
icza
113
@icza, это один из лучших ответов, которые я давно видел на SO!
астропаник
1
@MikeAtlas: следует избегать использования, deferкогда очевидно, что вам это не нужно. См. Grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx,
1
@ZanLynx спасибо за совет; хотя deferразблокировать мьютекс непосредственно перед или после вызова блокировки - это, по большей части, очень хорошая идея; Вы оба гарантированно не забудете разблокировать, но и разблокировать даже в середине фатальной функции без фатальной паники.
Майк Атлас
1
@RobbieV похоже, что этот код безопасен для потоков / горутингов, поскольку базовый общий источник уже является LockedSource, который реализует мьютекс ( golang.org/src/math/rand/rand.go:259 ).
adityajones
130

Вы можете просто написать код для этого. Этот код может быть немного проще, если вы хотите, чтобы при кодировании в 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))
}
Пол Ханкин
источник
30
Не забывайте о 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, она будет возвращать один и тот же результат все время. Я пробовал это на детской площадке и через некоторое время понял это. Это работало нормально в противном случае для меня. Надеюсь, это сэкономит кому-то время :)
Gaurav Sinha
18

Используйте пакет uniuri , который генерирует криптографически безопасные однородные (несмещенные) строки.

Отказ от ответственности: я автор пакета

dchest
источник
1
Кроме того, автор dchest - отличный разработчик, который выпустил несколько небольших полезных пакетов, подобных этой.
Рошамбо
16

Два возможных варианта (может быть больше, конечно):

  1. Вы можете использовать crypto/randпакет, который поддерживает чтение случайных байтовых массивов (из / dev / urandom) и ориентирован на криптографическую генерацию случайных чисел. см. http://golang.org/pkg/crypto/rand/#example_Read . Хотя это может быть медленнее, чем обычная генерация псевдослучайных чисел.

  2. Возьмите случайное число и хэшируйте его, используя md5 или что-то вроде этого.

Not_a_Golfer
источник
4

Следуя 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.

Крис
источник
2

Если вам нужны криптографически безопасные случайные числа, а точная кодировка является гибкой (скажем, с 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

Стивен Сорока
источник
Стоит отметить, что Go Playground всегда возвращает одно и то же случайное число, поэтому вы не увидите там разные случайные строки при разных выполнениях этого кода
TPPZ
2
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}
Кевин
источник
Почему оно генерирует n * 2 []byte?
М. Ростами
1

Вот мой путь) Используйте математический рэнд или крипто-рэнд, как хотите.

func randStr(len int) string {
    buff := make([]byte, len)
    rand.Read(buff)
    str := base64.StdEncoding.EncodeToString(buff)
    // Base 64 can be longer than len
    return str[:len]
}
Дима
источник
0

Если вы хотите добавить несколько символов в свой пул разрешенных символов, вы можете заставить код работать с любым, который предоставляет случайные байты через 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
}
0xcaff
источник
зачем это random % 64нужно?
Сунг Чо
2
Потому что 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 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 распределяет /

korzhao
источник
Здравствуйте! Добро пожаловать в StackOverflow. Несмотря на то, что вы добавили фрагмент кода, ваш ответ не включает никакого контекста о том, «как это работает» или «почему это так». Также, пожалуйста, помните, что вопрос задается на английском языке, поэтому ваши комментарии должны быть на английском языке.
Ченгиз Может
-2
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 выделяет / оп

user10987909
источник