Як генерувати випадкову рядок фіксованої довжини в Go?


300

Мені потрібно лише випадковий рядок символів (великі або малі літери), без цифр, у Go. Який найшвидший і найпростіший спосіб зробити це?


2
@VinceEmigh: Ось мета тема, яка обговорює основні питання. meta.stackoverflow.com/q/274645/395461 Особисто я вважаю, що основні питання добре, якщо вони написані добре та є тематичними. Подивіться на відповіді нижче, вони ілюструють купу речей, які були б корисні для когось нового. Для циклів введіть кастинг, make () тощо.
Шеннон Метьюс

2
@Shannon " Це питання не виявляє жодних зусиль для дослідження " (перша висококваліфікована відповідь за вашим посиланням) - саме про це я і мав на увазі. Він не виявляє жодних зусиль для дослідження. Немає зусиль (спроба чи навіть заявлення, що він дивився в Інтернеті, чого він, очевидно, не мав). Хоча це було б корисно для когось нового , цей сайт не зосереджений на навчанні нових людей. Він орієнтований на відповіді на конкретні проблеми / питання програмування, а не на навчальні посібники / посібники. Хоча це може бути використане для останнього, це не є фокусом, і тому це питання слід закрити. Натомість його ложкою /:
Вінс Емі

9
@VinceEmigh Я поставив це питання рік тому. Я шукав в Інтернеті випадкові рядки і теж читав документи. Але це було не корисно. Якщо я не писав у запитанні, то це не означає, що я не досліджував.
Аніш Шах

Відповіді:


809

Рішення Павла забезпечує просте загальне рішення.

Питання задає "найшвидший і найпростіший спосіб" . Звернімося також до найшвидшої частини. Ми дійдемо до свого останнього, найшвидшого коду в ітераційному порядку. Бенчмаркинг кожної ітерації можна знайти в кінці відповіді.

Усі рішення та код бенчмаркінгу можна знайти на майданчику Go Play . Код на дитячій площадці - це тестовий файл, а не виконуваний файл. Ви повинні зберегти його у файлі з ім'ям XX_test.goта запустити його

go test -bench . -benchmem

Передмова :

Найшвидше рішення не є рішенням, якщо вам просто потрібен випадковий рядок. Для цього ідеально рішення Павла. Це якщо продуктивність має значення. Хоча перші два кроки ( байт і залишок ) можуть бути прийнятним компромісом: вони покращують продуктивність приблизно на 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"

Зараз це вже велике поліпшення: ми могли б домогтися того, щоб він був conststringконстанти, але немає констант зрізу ). Як додатковий виграш, вираз len(letters)буде також const! (Вираз len(s)є постійним, якщо sє постійною струною.)

І якою ціною? Нічого взагалі. strings може бути індексовано, що індексує його байти, ідеально, саме те, що ми хочемо.

Наступне наше призначення виглядає так:

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пакетів), a 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в « Генезісі» та []byteв наступних рішеннях), а потім перетворюється в string. Це остаточне перетворення повинно зробити копію вмісту фрагмента, оскільки stringзначення незмінні, і якщо перетворення не зробило б копію, не можна було гарантувати, що вміст рядка не буде змінено через його початковий фрагмент. Докладніше див. Як перетворити рядок utf8 в байт []? і golang: [] байт (рядок) проти [] байт (* рядок) .

Перейти 1.10 введено strings.Builder. strings.Builderновий тип, який ми можемо використовувати для створення вмісту, stringподібного до bytes.Buffer. Це робиться всередині, використовуючи a []byte, і коли ми закінчимо, ми можемо отримати остаточне stringзначення за допомогою його Builder.String()методу. Але що в цьому круто - це те, що він робить це, не виконуючи копію, про яку ми говорили вище. Це наважується зробити це, оскільки байтовий фрагмент, який використовується для створення вмісту рядка, не піддається впливу, тому гарантується, що ніхто не може його змінити ненавмисно чи зловмисно, щоб змінити створений "незмінний" рядок.

Отже, наступна наша ідея полягає в тому, щоб не будувати випадковий рядок у зрізі, а за допомогою а. 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, такий же, як ми зробили самі. Так що в основному це робиться через a, 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(розкриття: я автор).

Але код Benchmark все ще показує, що ми не виграємо. Чому так?

Відповідь на останнє запитання полягає в тому, що він 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(), використовує одну шосту пам'яті і половину , як кілька розподілів . Місію виконано.


8
@RobbieV Yup, оскільки використовується спільний доступ rand.Source. Кращим рішенням було б перехід rand.Sourceна RandStringBytesMaskImprSrc()функцію, і таким чином не потрібно блокувати, а отже, продуктивність / ефективність не досягається. Кожна програма може мати своє Source.
icza

113
@icza, це одна з найкращих відповідей, яку я давно бачив на SO!
астропанічний

1
@MikeAtlas: Слід уникати використання, deferколи очевидно, що він вам не потрібен. Дивіться grokbase.com/t/gg/golang-nuts/158zz5p42w/…
Zan Lynx

1
@ZanLynx thx для наконечника; хоча deferрозблокувати мютекс безпосередньо перед або після виклику блокування IMO здебільшого дуже гарна ідея; Ви гарантовано не будете забувати розблокувати, але також розблокувати навіть у не фатальній панічній середині функції.
Майк Атлас

1
@RobbieV виглядає так, що цей код є безпечним для потоків / програм, тому що основним спільним джерелом є вже LockedSource, який реалізує mutex ( 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(наприклад, опція @ Not_A_Golfer 1).
twotwotwo

1
@EvanLin Хіба це не можна здогадатися? Якщо мені доведеться засіяти генератор, то зловмисник міг би вгадати час, з яким я його висіваю, і передбачити той самий вихід, який я генерую.
Матей

4
Зауважте, що якщо ви спробуєте вищевказану програму з насінням, на ігровій майданчику вона буде постійно повертати однаковий результат. Я спробував це на дитячому майданчику і через деякий час зрозумів це. Мені це нормально спрацювало. Сподіваюсь, це економить якийсь час :)
Gaurav Sinha

18

Використовуйте пакет uniuri , який генерує криптографічно захищені рівномірні (неупереджені) рядки.

Відмова: Я автор пакету


1
Убік: автор, dchest, є чудовим розробником і створив ряд маленьких, корисних пакетів на кшталт цього.
Рошамбо

16

Два можливі варіанти (може бути, звичайно, більше):

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

  2. Візьміть випадкове число і хеште його, використовуючи md5 або щось подібне.


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 чудово), ви можете точно розрахувати, яка довжина випадкових символів вам потрібна з потрібного розміру виводу.

Текст основи 64 на 1/3 довший, ніж базовий 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. Ви можете виконати однаковий розрахунок розміру з тим, скільки бітів потрібно для представлення набору символів. Розрахунок відношення для будь-якої довільної схеми становить:) ratio = 8 / log2(len(charset)).

Хоча обидва ці рішення є безпечними, простими, вони повинні бути швидкими, і не витрачайте свій криптовалютний пул ентропії.

Ось майданчик показує, що він працює для будь-якого розміру. https://play.golang.org/p/i61WUVR8_3Z


Варто згадати, що Go Playground завжди повертає одне і те ж випадкове число, тому ви не побачите там різних випадкових рядків при різних виконаннях цього коду
TPPZ


1

Ось мій спосіб) Використовуйте математичний rand або crypto rand за своїм бажанням.

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
}

навіщо це random % 64потрібно?
Сун Чо

2
Тому що len(encodeURL) == 64. Якщо цього random % 64не зробити, це randomPosможе бути> = 64 і викликати паніку поза межами часу виконання.
0xcaff

-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 Б / оп 1 алоків / оп

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.