문제

에서,이동 string 은 기본 유형을 의미하는,그것은 읽기만,모든 조작을 만들 것입니다 새로운 문자열입니다.

그렇다면 내가 원하는 문자열을 연결하지 않고 많은 시간을 알 길이의 결과 문자열,가장 좋은 방법은 무엇일지?

순진한 방법이 될 것이다:

s := ""
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s

그러나지 않는 것은 매우 효율적입니다.

도움이 되었습니까?

해결책

2018 년에 추가되었습니다

Go 1.10에서 a strings.Builder 유형, 자세한 내용은이 답변을 살펴보십시오..

201x 이전 답변

가장 좋은 방법은 사용하는 것입니다 bytes 패키지. 그것은 있습니다 Buffer 구현하는 유형 io.Writer.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer

    for i := 0; i < 1000; i++ {
        buffer.WriteString("a")
    }

    fmt.Println(buffer.String())
}

이것은 O (n) 시간으로 수행합니다.

다른 팁

문자열을 연결하는 가장 효율적인 방법은 내장 기능을 사용하는 것입니다. copy. 내 테스트에서 그 접근법은 사용하는 것보다 ~ 3 배 빠릅니다. bytes.Buffer 그리고 연산자를 사용하는 것보다 훨씬 빠르기 (~ 12,000x) +. 또한 메모리가 적습니다.

나는 만들었다 테스트 케이스 이것을 증명하기 위해 결과는 다음과 같습니다.

BenchmarkConcat  1000000    64497 ns/op   502018 B/op   0 allocs/op
BenchmarkBuffer  100000000  15.5  ns/op   2 B/op        0 allocs/op
BenchmarkCopy    500000000  5.39  ns/op   0 B/op        0 allocs/op

아래는 테스트를위한 코드입니다.

package main

import (
    "bytes"
    "strings"
    "testing"
)

func BenchmarkConcat(b *testing.B) {
    var str string
    for n := 0; n < b.N; n++ {
        str += "x"
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); str != s {
        b.Errorf("unexpected result; got=%s, want=%s", str, s)
    }
}

func BenchmarkBuffer(b *testing.B) {
    var buffer bytes.Buffer
    for n := 0; n < b.N; n++ {
        buffer.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); buffer.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", buffer.String(), s)
    }
}

func BenchmarkCopy(b *testing.B) {
    bs := make([]byte, b.N)
    bl := 0

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        bl += copy(bs[bl:], "x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); string(bs) != s {
        b.Errorf("unexpected result; got=%s, want=%s", string(bs), s)
    }
}

// Go 1.10
func BenchmarkStringBuilder(b *testing.B) {
    var strBuilder strings.Builder

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        strBuilder.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); strBuilder.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", strBuilder.String(), s)
    }
}

Go 1.10부터 시작하여 a strings.Builder, 여기.

빌더는 쓰기 방법을 사용하여 스트링을 효율적으로 빌드하는 데 사용됩니다. 메모리 복사를 최소화합니다. 제로 값은 사용할 준비가되었습니다.


용법:

거의 동일합니다 bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    var str strings.Builder

    for i := 0; i < 1000; i++ {
        str.WriteString("a")
    }

    fmt.Println(str.String())
}

메모: 기본 데이터를 캐시하므로 StringBuilder 값을 복사하지 마십시오. StringBuilder 값을 공유하려면 포인터를 사용하십시오.


StringBuilder 메소드 및 인터페이스 지원 지원 :

이 방법은 기존 인터페이스를 염두에두고 구현되어 코드에서 새 빌더로 쉽게 전환 할 수 있습니다.


제로 가치 사용 :

var buf strings.Builder

바이트의 차이점 .buffer :

  • 성장하거나 재설정 할 수 있습니다.

  • ~ 안에 bytes.Buffer, 다음과 같은 기본 바이트에 액세스 할 수 있습니다. (*Buffer).Bytes(); strings.Builder 이 문제를 방지합니다. 때로는 이것은 문제가되지 않고 대신에 원합니다 (예를 들어 바이트가 io.Reader 등).

  • 또한 실수로 복사하는 것을 방지하는 카피 체크 메커니즘이 내장되어 있습니다 (func (b *Builder) copyCheck() { ... }).


소스 코드를 확인하십시오 여기.

문자열 패키지에는 라이브러리 기능이 있습니다. Join: http://golang.org/pkg/strings/#join

코드를보십시오 Join Kinopiko가 다음과 같은 기능을 부여하는 유사한 접근법을 보여줍니다. https://golang.org/src/strings/strings.go#l420

용법:

import (
    "fmt";
    "strings";
)

func main() {
    s := []string{"this", "is", "a", "joined", "string\n"};
    fmt.Printf(strings.Join(s, " "));
}

$ ./test.bin
this is a joined string

방금 내 자신의 코드 (재귀 트리 워크)에 위에 게시 된 상단 답변을 벤치마킹했으며 간단한 동의 연산자는 실제로보다 빠릅니다. BufferString.

func (r *record) String() string {
    buffer := bytes.NewBufferString("");
    fmt.Fprint(buffer,"(",r.name,"[")
    for i := 0; i < len(r.subs); i++ {
        fmt.Fprint(buffer,"\t",r.subs[i])
    }
    fmt.Fprint(buffer,"]",r.size,")\n")
    return buffer.String()
}

다음 코드는 0.81 초가 걸렸습니다.

func (r *record) String() string {
    s := "(\"" + r.name + "\" ["
    for i := 0; i < len(r.subs); i++ {
        s += r.subs[i].String()
    }
    s += "] " + strconv.FormatInt(r.size,10) + ")\n"
    return s
} 

0.61 초 밖에 걸리지 않았습니다. 이것은 아마도 새로운 것을 만드는 오버 헤드 때문일 것입니다. BufferString.

업데이트: 나는 또한 벤치마킹했다 join 기능과 0.54 초 안에 실행되었습니다.

func (r *record) String() string {
    var parts []string
    parts = append(parts, "(\"", r.name, "\" [" )
    for i := 0; i < len(r.subs); i++ {
        parts = append(parts, r.subs[i].String())
    }
    parts = append(parts, strconv.FormatInt(r.size,10), ")\n")
    return strings.Join(parts,"")
}

바이트의 큰 조각을 만들고 문자열 슬라이스를 사용하여 짧은 문자열의 바이트를 복사 할 수 있습니다. "Expext Go"에 제공된 함수가 있습니다.

func Append(slice, data[]byte) []byte {
    l := len(slice);
    if l + len(data) > cap(slice) { // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2);
        // Copy data (could use bytes.Copy()).
        for i, c := range slice {
            newSlice[i] = c
        }
        slice = newSlice;
    }
    slice = slice[0:l+len(data)];
    for i, c := range data {
        slice[l+i] = c
    }
    return slice;
}

그런 다음 작업이 완료되면 사용하십시오 string ( ) 바이트의 큰 조각에서 다시 문자열로 변환합니다.

이것은 먼저 전체 버퍼 크기를 알고 있거나 계산할 필요가없는 가장 빠른 솔루션입니다.

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)

기준, 그것은 사본 솔루션보다 20% 느리지만 (6.72ns가 아닌 부록 당 8.1ns) Bytes.Buffer를 사용하는 것보다 여전히 55% 더 빠릅니다.

package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}

참고로 추가 2018 년

에서 가 1.10 가 strings.Builder 유형 를 살펴보시기 바랍 이 대답에 대한 더 자세히.

Pre-201x 응답

벤치마크의 코드를@cd1 및 다른 답변은 잘못입니다. b.N 지에서 설정한 벤치마크 기능입니다.그것은 세트에 의하여 이동 테스트 도구의 동적으로 결정하는 경우에는 실행 시간의 시험은 안정되어 있습니다.

벤치마크 기능을 실행해야 하는 동일한 테스트 b.N 시간 및 시험 루프 안에서 동일해야에 대한 각각의 반복이다.그래서 나는 그것을 해결을 추가하여 inner loop.또한 추가 벤치마크에 대한 몇 가지 다른 솔루션:

package main

import (
    "bytes"
    "strings"
    "testing"
)

const (
    sss = "xfoasneobfasieongasbg"
    cnt = 10000
)

var (
    bbb      = []byte(sss)
    expected = strings.Repeat(sss, cnt)
)

func BenchmarkCopyPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        bs := make([]byte, cnt*len(sss))
        bl := 0
        for i := 0; i < cnt; i++ {
            bl += copy(bs[bl:], sss)
        }
        result = string(bs)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppendPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, cnt*len(sss))
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkCopy(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
        for i := 0; i < cnt; i++ {
            off := len(data)
            if off+len(sss) > cap(data) {
                temp := make([]byte, 2*cap(data)+len(sss))
                copy(temp, data)
                data = temp
            }
            data = data[0 : off+len(sss)]
            copy(data[off:], sss)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppend(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64)
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.Write(bbb)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWriteString(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkConcat(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < cnt; i++ {
            str += sss
        }
        result = str
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

환경 OS X10.11.6,2.2GHz,Intel Core i7

테스트 결과는:

BenchmarkCopyPreAllocate-8         20000             84208 ns/op          425984 B/op          2 allocs/op
BenchmarkAppendPreAllocate-8       10000            102859 ns/op          425984 B/op          2 allocs/op
BenchmarkBufferPreAllocate-8       10000            166407 ns/op          426096 B/op          3 allocs/op
BenchmarkCopy-8                    10000            160923 ns/op          933152 B/op         13 allocs/op
BenchmarkAppend-8                  10000            175508 ns/op         1332096 B/op         24 allocs/op
BenchmarkBufferWrite-8             10000            239886 ns/op          933266 B/op         14 allocs/op
BenchmarkBufferWriteString-8       10000            236432 ns/op          933266 B/op         14 allocs/op
BenchmarkConcat-8                     10         105603419 ns/op        1086685168 B/op    10000 allocs/op

결론:

  1. CopyPreAllocate 가장 빠른 방법입니다; AppendPreAllocate 은 아주 가까이 No.1,하지만 그것은 쉽게 쓰는 코드입니다.
  2. Concat 은 정말 나쁜 성능 모두에 대한 속도 메모리를 사용합니다.그것을 사용하지 않는다.
  3. Buffer#WriteBuffer#WriteString 기본적으로 같은 속도에서,반대로 무엇을@Dani-Br 에서 말했다.고려 string 실제로 []byte 에 가서,그것은 의미가 있습니다.
  4. 바이트입니다.기본적으로 버퍼를 사용하여 동일한 솔루션으로 Copy 추가적 책 유지 및 기타 물건입니다.
  5. CopyAppend 사용 부트스트랩 크기 64 개의 것으로,같은 바이트입니다.버퍼
  6. Append 더 많은 메모리를 사용하고 할당,나는 그것에 관련된 성장하는 알고리즘을 사용.그것은 성장 메모리 한 빨리 바이트입니다.버퍼

의 제안:

  1. 에 대한 간단한 작업은 무엇으로 OP 원,내가 사용하는 것 AppendAppendPreAllocate.그것은 충분히 빠르고 사용하기 쉽습니다.
  2. 필요한 경우 읽기 및 쓰기를 버퍼는 동시에,사용 bytes.Buffer 물론입니다.어떻게 그것을 위해 설계되었습니다 것입니다.

나의 원래 제안은했다

s12 := fmt.Sprint(s1,s2)

그러나 위의 답변 사용 BYTES.BUFFER- writestring () 가장 효율적인 방법입니다.

나의 초기 제안은 반사와 유형 스위치를 사용합니다. 보다 (p *pp) doPrint 그리고 (p *pp) printArg
내가 순진하게 생각했듯이 기본 유형에 대한 범용 스트링거 () 인터페이스는 없습니다.

적어도 Sprint () 내부적 바이트를 사용합니다 .buffer. 따라서

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`

메모리 할당 측면에서 허용됩니다.

=> Sprint () 연결을 사용하여 빠른 디버그 출력에 사용할 수 있습니다.
=> 그렇지 않으면 BYTES.BUFFER ... Writestring을 사용하십시오

CD1의 답변을 확장 : copy () 대신에 부록 ()을 사용할 수 있습니다. 부록 ()은 더 큰 사전 조항을 만들어 조금 더 많은 메모리 비용이 들지만 시간을 절약합니다. 나는 추가했다 두 개의 벤치 마크 당신의 꼭대기에. 로컬로 실행하십시오

go test -bench=. -benchtime=100ms

내 ThinkPad T400S에서는 다음과 같습니다.

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op

이것은 @CD1에서 제공하는 벤치 마크의 실제 버전입니다.Go 1.8, linux x86_64) @icza와 @pickboy가 언급 한 버그 수정.

Bytes.Buffer 오직 7 직접 문자열 연결보다 빠른 시간 + 운영자.

package performance_test

import (
    "bytes"
    "fmt"
    "testing"
)

const (
    concatSteps = 100
)

func BenchmarkConcat(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < concatSteps; i++ {
            str += "x"
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for i := 0; i < concatSteps; i++ {
            buffer.WriteString("x")
        }
    }
}

타이밍 :

BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op

goutils.joinbetween

 func JoinBetween(in []string, separator string, startIndex, endIndex int) string {
    if in == nil {
        return ""
    }

    noOfItems := endIndex - startIndex

    if noOfItems <= 0 {
        return EMPTY
    }

    var builder strings.Builder

    for i := startIndex; i < endIndex; i++ {
        if i > startIndex {
            builder.WriteString(separator)
        }
        builder.WriteString(in[i])
    }
    return builder.String()
}

나는 다음을 사용하여 그것을한다 :-

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
package main

import (
"fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    result := make([]byte, 0)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)

    fmt.Println(string(result))
}

메모리 할당 통계를 통한 벤치 마크 결과. 벤치 마크 코드를 확인하십시오 github.

성능을 최적화하려면 strings.builder를 사용하십시오.

go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/hechen0/goexp/exps
BenchmarkConcat-8                1000000             60213 ns/op          503992 B/op          1 allocs/op
BenchmarkBuffer-8               100000000               11.3 ns/op             2 B/op          0 allocs/op
BenchmarkCopy-8                 300000000                4.76 ns/op            0 B/op          0 allocs/op
BenchmarkStringBuilder-8        1000000000               4.14 ns/op            6 B/op          0 allocs/op
PASS
ok      github.com/hechen0/goexp/exps   70.071s
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))

strings.Join() "문자열"패키지에서

유형 불일치가있는 경우 (int 및 문자열에 가입하려는 경우와 같이) RandomType (변경하려는 것)를 수행합니다.

전:

package main

import (
    "fmt"
    "strings"
)

var intEX = 0
var stringEX = "hello all you "
var stringEX2 = "people in here"


func main() {
    s := []string{stringEX, stringEX2}
    fmt.Println(strings.Join(s, ""))
}

출력 :

hello all you people in here
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top