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

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

Ответ 1

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

Вопрос требует "самый быстрый и простой способ". Позвольте обратиться к самой быстрой части тоже. Мы дойдем до нашего финального, самого быстрого кода итеративным способом. Сравнительный анализ каждой итерации можно найти в конце ответа.

Все решения и код тестирования можно найти на игровой площадке Go. Код на игровой площадке - это тестовый файл, а не исполняемый файл. Вы должны сохранить его в файл с именем 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 может быть проиндексирована, что индексирует ее байты, идеально, именно то, что мы хотим.

Наш следующий пункт назначения выглядит следующим образом:

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 правильно инициализирован /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. strings.Builder новый тип, который мы можем использовать для создания содержимого string похожей на bytes.Buffer. Он делает это внутренне, используя []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, так же, как мы это делали сами. Таким образом, в основном это делается с помощью strings.Builder У strings.Builder есть некоторые накладные расходы, единственное, что мы переключили на strings.Builder - это предотвращение окончательного копирования фрагмента.

strings.Builder избегает окончательной копии, используя unsafe пакет:

// String returns the accumulated string.
func (b *Builder) String() string {
    return *(*string)(unsafe.Pointer(&b.buf))
}

Дело в том, что мы тоже можем сделать это сами. Таким образом, идея здесь состоит в том, чтобы вернуться к построению случайной строки в []byte, но когда мы закончим, не конвертируем ее в string для возврата, а делаем небезопасное преобразование: получаем 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 байт. Мы можем рассчитать вероятность того, что случайный индекс не будет использоваться (см. Выше), поэтому мы можем запросить больше, что будет "более вероятно", будет достаточно (если окажется, что это не так, мы повторим процесс). Например, мы можем обработать фрагмент байта как "поток битов", для которого у нас есть хорошая сторонняя библиотека: github.com/icza/bitio (раскрытие: я автор).

Но контрольный код все еще показывает, что мы не выигрываем. Почему это так?

Ответ на последний вопрос заключается в том, что rand.Read() использует цикл и продолжает вызывать Source.Int63() пока не заполнит переданный фрагмент. Именно то, что RandStringBytesMaskImprSrc() решение 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, мы получаем strings.Builder скорость 3,5%, но мы также достигли 50% сокращения использования памяти и ее распределения! Это мило!

Наконец, если мы решимся использовать unsafe пакет вместо strings.Builder, мы снова получим хорошие 14%.

Сравнивая окончательное и первоначальное решения: RandStringBytesMaskImprSrcUnsafe() в 6,3 раза быстрее, чем RandStringRunes(), использует одну шестую память и вдвое меньше ресурсов. Миссия выполнена.

Ответ 2

Вы можете просто написать код для этого. Этот код может быть немного проще, если вы хотите, чтобы при кодировании в 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))
}

Ответ 3

Возможны две возможности (возможно, более естественно):

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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]
}

Ответ 7

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

Ответ 8

Кроме того, я нашел пакет, в котором есть множество методов для манипулирования поддельными данными. Он был полезен для загрузки базы данных при разработке https://github.com/Pallinder/go-randomdata. Может быть полезно и для кого-то другого.

Ответ 9

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

Ответ 10

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 выделяет/оп