Go では、 astring
はプリミティブ型です。つまり、読み取り専用であり、それを操作するたびに新しい文字列が作成されます。
結果の文字列の長さを知らずに文字列を何度も連結したい場合、どのように行うのが最善の方法でしょうか?
単純な方法は次のとおりです。
var s string
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
しかし、それはあまり効率的ではないようです。
Go では、 astring
はプリミティブ型です。つまり、読み取り専用であり、それを操作するたびに新しい文字列が作成されます。
結果の文字列の長さを知らずに文字列を何度も連結したい場合、どのように行うのが最善の方法でしょうか?
単純な方法は次のとおりです。
var s string
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
しかし、それはあまり効率的ではないようです。
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) 時間で完了します。
事前に割り当てようとしている文字列の全長がわかっている場合、文字列を連結する最も効率的な方法は、組み込み関数を使用すること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)
}
}
文字列に効率的に変換したい文字列スライスがある場合は、このアプローチを使用できます。それ以外の場合は、他の回答を見てください。
と呼ばれる文字列パッケージにライブラリ関数がありますJoin
:
http://golang.org/pkg/strings/#Join
のコードを見ると、Join
Kinopiko が書いた 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
上記のトップアンサーを自分のコード(再帰的なツリーウォーク)でベンチマークしたところ、単純な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,"")
}
バイトの大きなスライスを作成し、文字列スライスを使用して短い文字列のバイトをそこにコピーできます。「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 ( )
バイトの大きなスライスで使用して、それを再び文字列に変換します。
これは、@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
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))
}
私は以下を使用してそれを行います:-
package main
import (
"fmt"
"strings"
)
func main (){
concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator.
fmt.Println(concatenation) //abc
}
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
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