it-source

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

criticalcode 2023. 5. 1. 21:29
반응형

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

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

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

이 질문은 "가장 빠르고 간단한 방법"을 묻는 것입니다.가장 빠른 부분도 말씀드리겠습니다.우리는 반복적인 방식으로 최종적이고 가장 빠른 코드에 도달할 수 있습니다.각 반복을 벤치마킹하는 방법은 답변의 끝에서 확인할 수 있습니다.

모든 솔루션과 벤치마킹 코드는 Go Playground에서 확인할 수 있습니다.Playground의 코드는 실행 파일이 아닌 테스트 파일입니다.이름이 지정된 파일에 저장해야 합니다.XX_test.go 로다니합으로 합니다.

go test -bench . -benchmem

서문:

랜덤 문자열만 필요한 경우 가장 빠른 솔루션은 이동식 솔루션이 아닙니다.그것을 위해, Paul의 해결책은 완벽합니다.성능이 중요한 경우입니다.처음 2단계(바이트나머지)는 허용 가능한 절충안이지만, 성능이 약 50% 향상됩니다(II의 정확한 수치 참조). 벤치마크 섹션)에서는 복잡성이 크게 증가하지 않습니다.

그렇기는 하지만, 가장 빠른 해결책이 필요하지 않더라도, 이 답을 읽어보는 것은 모험적이고 교육적일 수 있습니다.

I. 개선 사항

제네시스(런)

다시 한 번 말씀드리지만, 저희가 개선하고 있는 원래의 일반적인 솔루션은 다음과 같습니다.

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

바이트

임의의 문자열을 선택하고 조합할 문자가 영어 알파벳의 대문자와 소문자만 포함하는 경우, 영어 알파벳 문자가 UTF-8 인코딩(Go가 문자열을 저장하는 방식)에서 바이트 1대 1로 매핑되기 때문에 바이트로만 작업할 수 있습니다.

그래서 대신에:

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

사용할 수 있습니다.

var letters = []byte("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)
}

나머지

이전 솔루션에서는 어떤 딜러에게 어떤 딜러를 호출하여 임의의 문자를 지정할 수 있는 임의의 번호를 얻습니다.

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

가 있고 더 , 단점은 같지 예: 다음같예빠르며훨, 문것않같다입니다는가정지게정이하확확률이과모자의, 작이동고것단점든은하씬은예uming▁thisass(것다니입가정▁willability▁of▁not▁works▁the▁that▁and:▁the▁is▁the).rand.Int63()확률이 동일한 모든 63비트 숫자를 생성합니다.,52▁than다니▁much-▁is작습보다 훨씬 작습니다.1<<63 - 1그래서 실제로는 이것은 완벽하게 좋습니다.

이를 보다 쉽게 이해하기 위해 다음과 같은 범위에서 임의의 숫자를 원한다고 가정합니다.0..5비트를 3이 됩니다.0..1범위에서 보다 두 배의 확률로.2..5의 랜덤 비트를 하여 5개의 범위 숫자들은 의 랜 사 범 내 용 트 의 숫 자 위 비 덤 개 ▁5 ▁using0..1 것할니다와 함께 할 것입니다.6/32과 범위의 2..5와 함께5/32이제는 원하는 것에 더 가까운 확률.비트 수를 늘리면 63비트에 도달할 때 이는 무시해도 될 정도로 중요하지 않습니다.

마스킹

이전 솔루션을 기반으로, 우리는 글자 수를 나타내는 데 필요한 수의 랜덤 숫자의 가장 낮은 비트만 사용함으로써 동일한 문자 분포를 유지할 수 있습니다.를 들어 글자가 할 수 있습니다: 예를들어개, 비문자있가는, 의경다있표니어우 6시가됩야트다.52 = 110100b는 그서우리반비환숫 6자트사것다입이 반환한 숫자의 가장 낮은 입니다.rand.Int63()그리고 문자의 균등한 분포를 유지하기 위해, 우리는 숫자가 범위에 포함되는 경우에만 "수용"합니다.0..len(letterBytes)-1가장 낮은 비트가 크면 이를 폐기하고 새로운 난수를 쿼리합니다.

이 가낮 은 비 의 가 능 성 다 은 같 다 과 같 습 니 다 음 나 거 장 과 음 트 ▁of ▁to ▁note ▁the ▁to ▁that ▁chance ▁than ▁equal 니 ▁be 다 ▁bits ▁the 가 ▁greater 습 ▁or 장 ▁lowestlen(letterBytes)보다 .0.5일반적으로(0.25평균), 즉, 이 경우가 그렇다 하더라도 이 "잘못된" 경우를 반복하면 좋은 숫자를 찾지 못할 가능성이 줄어듭니다.나 뒤에n반복, 우리가 여전히 좋은 색인을 가지고 있지 않을 가능성은 훨씬 적습니다.pow(0.5, n)그리고 이건 단지 추정치가 높습니다., 낮은 않을 은 52자, 6자일 확률은 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)
}

마스킹 개선

은 이솔루개반은환비된의랜덤 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)
}

출처

개선된 마스킹은 꽤 좋습니다. 개선할 수 있는 것은 많지 않습니다.그럴 수는 있지만, 복잡할 가치는 없습니다.

이제 개선할 다른 것을 찾아보겠습니다.난수의 출처입니다.

기능을 제공하는 패키지가 있기 때문에 한 번의 통화로 필요한 만큼의 바이트를 얻을 수 있습니다.이것은 성능 면에서 도움이 되지 않을 것입니다.crypto/rand암호화 방식으로 안전한 의사 난수 생성기를 구현하므로 속도가 훨씬 느립니다.

자, 그럼 다음과 같이 합시다.math/rand꾸러미rand.Rand는 임의 비트의 소스로 사용합니다. rand.Source는 다을지인입니다스터이를 하는 인터페이스입니다.Int63() int64방법: 정확하고 우리가 가장 최근에 사용한 솔루션에 필요한 유일한 것입니다.

그래서 우리는 정말로 필요하지 않습니다.rand.Rand 중 함)rand 패지키), arand.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상태:

기본 소스는 여러 Groutine에서 동시에 사용해도 안전합니다.

가 따서기보소스가보다 .Source은 에의해얻수을있는▁be▁by는있▁obtained▁that로 얻을 수 있습니다.rand.NewSource()기본 소스는 동시 액세스/사용 시 안전성을 제공해야 하기 때문입니다.rand.NewSource()이것을 제공하지 않습니다 (따라서.Source더 빠를 가능성이 높습니다.)

활용하기strings.Builder

은 이의모솔은다반음환다니합을션루전든을 합니다.string컨텐츠가 처음으로 슬라이스에 내장된 경우([]rune창세기에, 그리고.[]byte의 용액에서에 "다솔루로에변다니환서됩션음로다변"로 변환합니다."로 합니다.string이 최종 변환은 슬라이스의 내용을 복사해야 합니다.string값은 불변이며 변환이 복사본을 만들지 않는 경우 문자열의 내용이 원래 슬라이스를 통해 수정되지 않는다고 보장할 수 없습니다.자세한 내용은 utf8 문자열을 []바이트로 변환하는 방법을 참조하십시오. 골랑: []바이트(문자열) vs []바이트(문자열).

소개된 Go strings.Builder1.10은 우리가 사용할 수 있는 새로운 유형입니다.string…과 유사한내부적으로 사용됩니다.[]byte콘텐츠를 구축하고, 우리가 끝나면, 우리는 결승전을 얻을 수 있습니다.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우리는 그것의 메소드를 호출하여, 그것이 큰 크기의 내부 슬라이스를 할당하도록 했습니다(랜덤 문자를 추가할 때 재할당을 방지하기 위해).

"모방"strings.Builder unsafe

strings.Builder 내서문 을작니다성으로 합니다.[]byte우리가 했던 것과 똑같습니다.그래서 기본적으로 a를 통해 그것을 합니다.strings.Builder우리가 바꾼 유일한 것은 약간의 오버헤드가 있습니다.strings.Builder이는 슬라이스의 최종 복사를 방지하기 위한 것입니다.

strings.Builder패키지를 사용하여 최종 복사본을 방지합니다.

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

문제는 우리 스스로도 할 수 있다는 것입니다.그래서 여기서의 아이디어는 무작위 문자열을 만드는 것으로 다시 전환하는 것입니다.[]byte하지만 우리가 끝났을 때, 그것을 로 변환하지 마세요.string변환을 ▁a다▁to를 얻습니다.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. 사용9rand.Read())

Go 1.7은 기능과 방법을 추가했습니다.더 나은 성능을 얻기 위해 한 단계에서 필요한 만큼의 바이트를 읽기 위해 이를 사용해야 합니다.

여기에는 작은 "문제"가 하나 있습니다. 몇 바이트가 필요한가요?우리는 이렇게 말할 수 있습니다: 출력 문자의 수만큼.문자 인덱스는 8비트(1바이트) 미만을 사용하기 때문에 이것은 상위 추정치라고 생각합니다.하지만 이 시점에서 우리는 이미 더 나빠지고 있으며(랜덤 비트를 얻는 것이 "어려운 부분"이기 때문에), 필요 이상의 것을 얻고 있습니다.

또한 모든 문자 인덱스의 동일한 분포를 유지하기 위해 사용할 수 없는 "가비지" 랜덤 데이터가 있을 수 있으므로 일부 데이터를 건너뛸 수 있으며, 따라서 모든 바이트 슬라이스를 통과할 때 부족하게 됩니다.우리는 "재귀적으로" 더 많은 랜덤 바이트를 얻어야 할 필요가 있습니다.그리고 이제 우리는 심지어 "단 한 번의 전화"를 잃고 있습니다.rand패키지" 이점...

우리는 우리가 획득한 무작위 데이터의 사용을 "어느 정도" 최적화할 수 있습니다.math.Rand()필요한 바이트 수(비트)를 추정할 수 있습니다. 한 글자가 필요합니다.letterIdxBits비트, 그리고 우리는 필요합니다.n편지, 그래서 우리는 필요합니다.n * letterIdxBits / 8.0반올림한 바이트 수랜덤 인덱스를 사용할 수 없는 확률을 계산할 수 있으므로(위 참조), "더 가능성이 높은" 더 많은 것을 요청할 수 있습니다(사용할 수 없는 것으로 판명되면 프로세스를 반복합니다).예를 들어 바이트 슬라이스를 "비트 스트림"으로 처리할 수 있습니다. 여기에는 좋은 타사 lib가 있습니다. (공개:제가 저자입니다.

하지만 벤치마크 코드는 여전히 우리가 이기지 못하고 있음을 보여줍니다.그것은 왜 그럴까?

마지막 질문에 대한 대답은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의개에서 를 대부분)때, (1개 63인 10랜) 을또덤비때트중사용모든할분대부는스나덱그의▁but또을때개용사▁(▁we할▁make▁random▁bits)▁when10▁(▁of▁all중▁the▁from▁63개▁indices▁useor▁of모▁most개든(.rand.Int63()call): 그것은 큰 속도를 냅니다: 3배.

우리가 (이 아닌,한다면, (기본값이 아닌, 새로운)으로 됩니다.rand.Sourcerand.Rand우리는 다시 21%를 얻습니다.

가 할경우를 사용한다면,strings.Builder속도는 3.5%에 불과하지만 메모리 사용량과 할당도 50%줄었습니다!좋네요!

가 감히 한다면,unsafestrings.Builder우리는 다시 14%를 얻습니다.

솔루션과 솔루션 비교: 최종솔과솔루션비교기초:RandStringBytesMaskImprSrcUnsafe()보다 6.3배빠릅니다.RandStringRunes()6분의 1메모리와 절반의 할당사용합니다.임무 완수했어.

코드를 작성하면 됩니다.이 코드는 UTF-8로 인코딩될 때 문자가 모두 단일 바이트라는 것에 의존하고자 하는 경우 조금 더 단순할 수 있습니다.

package main

import (
    "fmt"
    "time"
    "math/rand"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

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() {
    fmt.Println(randSeq(10))
}

패키지 uniuri를 사용하면 암호화된 보안 균일(편향되지 않음) 문자열을 생성할 수 있습니다.

고지 사항:나는 패키지의 작성자입니다.

중복 결과를 최소화하는 간단한 솔루션:

import (
    "fmt"
    "math/rand"
    "time"
)

func randomString(length int) string {
    rand.Seed(time.Now().UnixNano())
    b := make([]byte, length+2)
    rand.Read(b)
    return fmt.Sprintf("%x", b)[2 : length+2]
}

놀이터에서 확인하세요.

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

  1. 당신은 할 수 .crypto/rand(/dev/urandom에서) 랜덤 바이트 배열 읽기를 지원하고 암호화 랜덤 생성을 위해 조정된 패키지입니다.http://golang.org/pkg/crypto/rand/ #example_Read를 참조하십시오. 하지만 일반적인 유사 프로세서 번호 생성보다 느릴 수 있습니다.

  2. 임의의 숫자를 가져와서 md5나 이런 것을 사용하여 해시합니다.

암호화된 보안 난수를 사용하고 정확한 문자 집합이 유연한 경우(예: base64는 괜찮습니다) 원하는 출력 크기에서 필요한 임의 문자 길이를 정확히 계산할 수 있습니다.

기본 64 텍스트는 기본 256보다 1/3 더 깁니다. (2^8 대 2^6, 8비트/6비트 = 1.333 비율)

import (
    "crypto/rand"
    "encoding/base64"
    "math"
)

func randomBase64String(l int) string {
    buff := make([]byte, int(math.Ceil(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 대 2^4, 8비트/4비트 = 2배 비율)

import (
    "crypto/rand"
    "encoding/hex"
    "math"
)


func randomBase16String(l int) string {
    buff := make([]byte, int(math.Ceil(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/_yF_xxXer0Z

JavaScript 암호의 생성 암호에서 영감을 얻은 다른 버전:

package main

import (
    "crypto/rand"
    "fmt"
)

var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"

func shortID(length int) string {
    ll := len(chars)
    b := make([]byte, length)
    rand.Read(b) // generates len(b) random bytes
    for i := 0; i < length; i++ {
        b[i] = chars[int(b[i])%ll]
    }
    return string(b)
}

func main() {
    fmt.Println(shortID(18))
    fmt.Println(shortID(18))
    fmt.Println(shortID(18))
}

icza's훌륭하게 설명된 솔루션, 여기 사용하는 수정이 있습니다.crypto/randmath/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.

이것이 나의 방법입니다) 당신이 원하는 대로 수학 랜드나 암호 랜드를 사용하세요.

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

다음은 암호화된 보안 랜덤 문자열을 위한 간단하고 성능이 뛰어난 솔루션입니다.

package main

import (
    "crypto/rand"
    "unsafe"
    "fmt"
)

var alphabet = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func main() {
    fmt.Println(generate(16))
}

func generate(size int) string {
    b := make([]byte, size)
    rand.Read(b)
    for i := 0; i < size; i++ {
        b[i] = alphabet[b[i] % byte(len(alphabet))]
    }
    return *(*string)(unsafe.Pointer(&b))
}

벤치마크

Benchmark  95.2 ns/op      16 B/op      1 allocs/op
func Rand(n int) (str string) {
    b := make([]byte, n)
    rand.Read(b)
    str = fmt.Sprintf("%x", b)
    return
}

나는 보통 자본화 여부에 대한 선택권이 필요하면 이렇게 합니다.

func randomString(length int, upperCase bool) string {
    rand.Seed(time.Now().UnixNano())

    var alphabet string

    if upperCase {
        alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    } else {
        alphabet = "abcdefghijklmnopqrstuvwxyz"
    }

    var sb strings.Builder

    l := len(alphabet)

    for i := 0; i < length; i++ {
        c := alphabet[rand.Intn(l)]
        sb.WriteByte(c)
    }

    return sb.String()
}

그리고 만약 대문자가 필요하지 않다면 이렇게.

func randomString(length int) string {
        rand.Seed(time.Now().UnixNano())

        var alphabet string = "abcdefghijklmnopqrstuvwxyz"
        var sb strings.Builder

        l := len(alphabet)

        for i := 0; i < length; i++ {
                c := alphabet[rand.Intn(l)]
                sb.WriteByte(c)
        }

        return sb.String()
}

허용된 문자 풀에 몇 개의 문자를 추가할 의향이 있다면 io를 통해 임의의 바이트를 제공하는 모든 항목에서 코드를 작동시킬 수 있습니다.서는 더리를 합니다. 여기서 우리가 사용하고 있습니다.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
}

이것은 제 앱에서 인증서 번호를 생성할 때 사용했던 샘플 코드입니다.

func GenerateCertificateNumber() string {
    CertificateLength := 7
    t := time.Now().String()
    CertificateHash, err := bcrypt.GenerateFromPassword([]byte(t), bcrypt.DefaultCost)
    if err != nil {
        fmt.Println(err)
    }
    // Make a Regex we only want letters and numbers
    reg, err := regexp.Compile("[^a-zA-Z0-9]+")
    if err != nil {
        log.Fatal(err)
    }
    processedString := reg.ReplaceAllString(string(CertificateHash), "")
    fmt.Println(string(processedString))

    CertificateNumber := strings.ToUpper(string(processedString[len(processedString)-CertificateLength:]))
    fmt.Println(CertificateNumber)
    return CertificateNumber
}

탁월한 솔루션의 후속 조치로 아래에서 사용하고 있습니다.

func RandStringBytesMaskImprRandReaderUnsafe(length uint) (string, error) {
    const (
        charset     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        charIdxBits = 6                  // 6 bits to represent a letter index
        charIdxMask = 1<<charIdxBits - 1 // All 1-bits, as many as charIdxBits
        charIdxMax  = 63 / charIdxBits   // # of letter indices fitting in 63 bits
    )

    buffer := make([]byte, length)
    charsetLength := len(charset)
    max := big.NewInt(int64(1 << uint64(charsetLength)))

    limit, err := rand.Int(rand.Reader, max)
    if err != nil {
        return "", err
    }

    for index, cache, remain := int(length-1), limit.Int64(), charIdxMax; index >= 0; {
        if remain == 0 {
            limit, err = rand.Int(rand.Reader, max)
            if err != nil {
                return "", err
            }

            cache, remain = limit.Int64(), charIdxMax
        }

        if idx := int(cache & charIdxMask); idx < charsetLength {
            buffer[index] = charset[idx]
            index--
        }

        cache >>= charIdxBits
        remain--
    }

    return *(*string)(unsafe.Pointer(&buffer)), nil
}


func BenchmarkBytesMaskImprRandReaderUnsafe(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()

    const length = 16

    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            RandStringBytesMaskImprRandReaderUnsafe(length)
        }
    })
}

저는 많은 프로젝트에서 구글의 uuid 라이브러리를 사용하고 있으며, 이를 통해 다음과 같은 작업을 쉽게 수행할 수 있습니다.

uuid.NewString()[:length]

그것은 최소한 상자를 확인해서 가장 단순하게 만듭니다.

https://go.dev/play/p/5sWDsGqglAi

/*
    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.0ns/op 16 B/op 1 할당/

package main

import (
    "encoding/base64"
    "fmt"
    "math/rand"
    "time"
)

// customEncodeURL is like `bas64.encodeURL` 
// except its made up entirely of uppercase characters:
const customEncodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKL"

// Random generates a random string.
// It is not cryptographically secure.
func Random(n int) string {
    b := make([]byte, n)
    rand.Seed(time.Now().UnixNano())
    _, _ = rand.Read(b) // docs say that it always returns a nil error.

    customEncoding := base64.NewEncoding(customEncodeURL).WithPadding(base64.NoPadding)
    return customEncoding.EncodeToString(b)
}

func main() {
    fmt.Println(Random(16))
}
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))
}

벤치마크RandStr16-820000000 68.1ns/op 16 B/op 1 할당/op

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

반응형