864

Go では、 astringはプリミティブ型です。つまり、読み取り専用であり、それを操作するたびに新しい文字列が作成されます。

結果の文字列の長さを知らずに文字列を何度も連結したい場合、どのように行うのが最善の方法でしょうか?

単純な方法は次のとおりです。

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

しかし、それはあまり効率的ではないようです。

4

19 に答える 19

984

新しい方法:

Go 1.10 からstrings.Builderタイプがあります。詳細については、この回答をご覧ください

古い方法:

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) 時間で完了します。

于 2009-11-19T20:31:24.203 に答える
288

事前に割り当てようとしている文字列の全長がわかっている場合、文字列を連結する最も効率的な方法は、組み込み関数を使用することcopyです。事前に全長がわからない場合はcopy、を使用せず、代わりに他の回答を読んでください。

私のテストでは、このアプローチは operator を使用するよりも ~3 倍高速でbytes.Bufferあり、 operator を使用するよりもはるかに高速 (~12,000 倍) です+。また、メモリの使用量も少なくなります。

これを証明するテスト ケースを作成しました。結果は次のとおりです。

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)
    }
}
于 2014-05-25T17:22:27.607 に答える
147

文字列に効率的に変換したい文字列スライスがある場合は、このアプローチを使用できます。それ以外の場合は、他の回答を見てください。

と呼ばれる文字列パッケージにライブラリ関数がありますJoin: http://golang.org/pkg/strings/#Join

のコードを見ると、JoinKinopiko が書いた Append 関数への同様のアプローチが示されています: 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
于 2009-11-19T14:18:18.430 に答える
45

上記のトップアンサーを自分のコード(再帰的なツリーウォーク)でベンチマークしたところ、単純なconcat演算子は実際にはよりも高速です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,"")
}
于 2012-04-29T01:15:00.597 に答える
24

バイトの大きなスライスを作成し、文字列スライスを使用して短い文字列のバイトをそこにコピーできます。「Effective 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 ( )バイトの大きなスライスで使用して、それを再び文字列に変換します。

于 2009-11-19T03:57:38.933 に答える
4

これは、@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
于 2017-03-29T08:18:22.783 に答える
1
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))
}
于 2018-08-09T09:36:39.647 に答える
1

私は以下を使用してそれを行います:-

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
于 2018-01-26T00:52:48.383 に答える
-3
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
于 2013-09-04T09:34:09.330 に答える
-5

strings.Join()「strings」パッケージから

型の不一致がある場合 (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
于 2016-05-11T00:51:48.193 に答える