development

Go에서 고정 길이의 임의 문자열을 생성하는 방법은 무엇입니까?

big-blog 2020. 3. 31. 08:20
반응형

Go에서 고정 길이의 임의 문자열을 생성하는 방법은 무엇입니까?


Go에서 임의의 문자열 만 (대문자 또는 소문자), 숫자 없음을 원합니다. 가장 빠르고 간단한 방법은 무엇입니까?


Paul의 솔루션은 단순 하고 일반적인 솔루션을 제공합니다.

질문은 "가장 빠르고 간단한 방법"을 요구 합니다. 가장 빠른 부분도 다루겠습니다 . 반복적 인 방식으로 가장 빠른 최종 코드에 도달합니다. 각 반복 벤치마킹은 답변 끝에서 찾을 수 있습니다.

모든 솔루션과 벤치마킹 코드는 Go Playground 에서 찾을 수 있습니다 . 놀이터의 코드는 테스트 파일이며 실행 파일이 아닙니다. 이름이 지정된 파일로 저장 XX_test.go하고 다음을 실행해야합니다.

go test -bench . -benchmem

서문 :

임의의 문자열 만 필요한 경우 가장 빠른 솔루션은 사용하지 않는 솔루션입니다. 이를 위해 Paul의 솔루션은 완벽합니다. 성능이 중요한 경우입니다. 처음 두 단계 ( Bytes and Remainder )는 허용 가능한 타협 일 수 있지만 성능을 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. 바이트

임의 문자열에서 선택하고 어셈블 할 문자에 영어 알파벳의 대문자와 소문자 만 포함 된 경우 UTF-8 인코딩에서 영어 알파벳 문자가 1에서 1로 바이트로 맵핑되므로 바이트 만 사용할 수 있습니다. 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..55/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. 마스킹 개선

이전 솔루션은에서 반환 한 63 개의 임의 비트 중 가장 낮은 6 비트 만 사용합니다 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)
}

또한 노트는이 마지막 솔루션은 글로벌 (씨앗)를 초기화 할 필요가 없습니다 것을 Randmath/rand것을 사용하지 않는 (우리는 다음과 같이 패키지를 rand.Source제대로 시드 / 초기화).

여기서 주목할 사항이 하나 더 있습니다 : package doc of math/randstates :

기본 소스는 여러 고 루틴이 동시에 사용할 수 있습니다.

따라서 기본 소스는에 Source의해 얻을 수있는 것보다 느립니다 rand.NewSource(). 기본 소스는 동시 액세스 / 사용시 안전을 제공해야하지만 rand.NewSource()이를 제공하지 않기 때문에 Source반환되는 것이 더 빠를 수 있습니다.

7. 활용 strings.Builder

이전의 모든 솔루션은 돌려 string그 내용이 먼저 슬라이스에 내장되어 있습니다 ( []rune창세기 , 그리고 []byte이후의 솔루션), 다음으로 변환 string. 이 최종 변환은 string값을 변경할 수 없기 때문에 슬라이스 내용의 사본을 만들어야하며 , 변환이 사본을 만들지 않으면 문자열 내용이 원래 슬라이스를 통해 수정되지 않을 수도 있습니다. 자세한 내용은 utf8 문자열을 [] 바이트로 변환하는 방법을 참조하십시오 . golang [] 바이트 (문자열)] 바이트 (* 문자열) 대 .

이동 1.10 도입 strings.Builder. 와 비슷한 strings.Builder내용을 만드는 데 사용할 수있는 새로운 유형 입니다. 내부적으로 a를 사용하여 수행하며 완료되면 해당 방법을 사용하여 최종 값을 얻을 수 있습니다 . 그러나 멋진 점은 위에서 언급 한 사본을 수행하지 않고이 작업을 수행한다는 것입니다. 문자열의 내용을 작성하는 데 사용 된 바이트 슬라이스가 노출되지 않기 때문에 감히 또는 아무도 악의적으로 생성 된 "불변의"문자열을 변경하여이를 수정할 수 없다는 것이 보장됩니다.stringbytes.Buffer[]bytestringBuilder.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()
}

new을 만든 후에 strings.BuidlerBuilder.Grow()메소드를 호출 하여 임의의 문자를 추가 할 때 재 할당을 피하기 위해 충분히 큰 내부 슬라이스를 할당해야합니다.

8. "모방" strings.Builder패키지unsafe

strings.Builder[]byte우리와 마찬가지로 내부에 문자열을 작성합니다 . 따라서 기본적으로 a strings.Builder통해 수행하면 약간의 오버 헤드가 발생합니다. 스위치로 전환 한 유일한 것은 strings.Builder슬라이스의 최종 복사를 피하는 것입니다.

strings.Builderpackage를 사용하여 최종 사본을 피하십시오 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.7rand.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()비동기 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 %의 성능 향상을 얻을 수 있으며 메모리 요구량은 1/3로 떨어집니다 .

제거 rand.Intn()하고 rand.Int63()대신 사용하면 20 % 더 증가합니다.

마스킹 (그리고 큰 지수의 경우 반복)은 약간의 속도 저하 (반복 호출로 인해) : -22 % ...

그러나 63 개의 임의 비트 (한 번의 rand.Int63()호출 에서 10 개의 인덱스)를 모두 (또는 대부분) 사용하면 시간이 3 배 빨라집니다 .

rand.Source대신 ( 기본이 아닌 새로운)로 정착하면 rand.Rand다시 21 % 증가합니다.

우리가 사용하는 경우 strings.Builder, 우리는 작은 이득 3.5 %를속도 , 그러나 우리는 또한 달성 50 %의 메모리 사용 및 할당에 감소! 멋지다!

마지막으로 우리가 unsafe대신에 패키지를 사용한다면 strings.Builder, 다시 14 % 좋은 결과를 얻을 수 있습니다.

초기 용액의 최종 비교 : RandStringBytesMaskImprSrcUnsafe()6.3 배 빠르게 보다 RandStringRunes()사용 육분의 일 메모리 및 거의 절반을 할당 . 임무 완수.


코드를 작성할 수 있습니다. 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))
}

두 가지 가능한 옵션 (물론 더있을 수 있음) :

  1. crypto/rand/ dev / urandom에서 임의 바이트 배열 읽기를 지원 하는 패키지를 사용하여 암호화 임의 생성에 적합합니다. http://golang.org/pkg/crypto/rand/#example_Read를 참조하십시오 . 그래도 정상적인 의사 난수 생성보다 느릴 수 있습니다.

  2. 임의의 숫자를 가져 와서 md5 또는 이와 유사한 것을 사용하여 해시하십시오.


암호화 적으로 안전한 균일 (비 편향) 문자열을 생성하는 패키지 uniuri를 사용하십시오 .

면책 조항 : 나는 패키지의 저자입니다


icza's훌륭하게 설명 된 해결책에 따라 다음 crypto/rand대신 사용하는 수정 사항이 있습니다 math/rand.

const (
    letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
    letterIdxBits = 6                    // 6 bits to represent 64 possibilities / indexes
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)

func SecureRandomAlphaString(length int) string {

    result := make([]byte, length)
    bufferSize := int(float64(length)*1.3)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            randomBytes = SecureRandomBytes(bufferSize)
        }
        if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
            result[i] = letterBytes[idx]
            i++
        }
    }

    return string(result)
}

// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
    var randomBytes = make([]byte, length)
    _, err := rand.Read(randomBytes)
    if err != nil {
        log.Fatal("Unable to generate random bytes")
    }
    return randomBytes
}

보다 일반적인 해결책을 원한다면 문자열을 만들기 위해 문자 바이트 조각을 전달할 수 있습니다.

// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {

    // Compute bitMask
    availableCharLength := len(availableCharBytes)
    if availableCharLength == 0 || availableCharLength > 256 {
        panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
    }
    var bitLength byte
    var bitMask byte
    for bits := availableCharLength - 1; bits != 0; {
        bits = bits >> 1
        bitLength++
    }
    bitMask = 1<<bitLength - 1

    // Compute bufferSize
    bufferSize := length + length / 3

    // Create random string
    result := make([]byte, length)
    for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
        if j%bufferSize == 0 {
            // Random byte buffer is empty, get a new one
            randomBytes = SecureRandomBytes(bufferSize)
        }
        // Mask bytes to get an index into the character slice
        if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
            result[i] = availableCharBytes[idx]
            i++
        }
    }

    return string(result)
}

자신의 무작위 소스를 전달하려면 io.Reader을 사용 하는 대신 위의 내용을 수정하는 것이 쉽지 crypto/rand않습니다.


당신이 원하는 경우 암호 보안 (예를 들어, base64로 괜찮) 임의의 숫자, 그리고 정확한 캐릭터 세트가 유연 정확히 임의의 문자의 길이가 원하는 출력 크기에서 필요한 계산할 수 있습니다.

기본 64 텍스트는 기본 256보다 1/3이 길다. (2 ^ 8 vs 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 진수를 원한다면 16 진수는 256보다 2 배 더 깁니다. (2 ^ 8 vs 2 ^ 4; 8 비트 / 4 비트 = 2x 비율)

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 to baseN 인코더가있는 경우 임의의 문자 집합으로 확장 할 수 있습니다. 문자 집합을 나타내는 데 필요한 비트 수로 동일한 크기 계산을 수행 할 수 있습니다. 임의의 문자 집합에 대한 비율 계산은 다음과 같습니다. ratio = 8 / log2(len(charset))).

이 두 가지 솔루션 모두 안전하고 단순하지만 빠르며 암호화 엔트로피 풀을 낭비하지 마십시오.

모든 크기에서 작동하는 놀이터는 다음과 같습니다. https://play.golang.org/p/i61WUVR8_3Z


여기 내 방법이 있습니다) 원하는대로 수학 랜드 또는 암호 랜드를 사용하십시오.

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

허용되는 문자 풀에 몇 개의 문자를 추가하려는 경우 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
}

또한 가짜 데이터를 조작하는 방법이 많은 패키지를 찾았습니다. https://github.com/Pallinder/go-randomdata 를 개발하는 동안 데이터베이스 시드에 유용하다는 것을 알았습니다 . 다른 사람에게도 도움이 될 수 있습니다


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

벤치 마크 LandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op

참고 URL : https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go

반응형