So verketten Sie Zeichenfolgen in go effizient

727

In Go ist a stringein primitiver Typ, was bedeutet, dass er schreibgeschützt ist und bei jeder Manipulation eine neue Zeichenfolge erstellt wird.

Wenn ich also Zeichenfolgen viele Male verketten möchte, ohne die Länge der resultierenden Zeichenfolge zu kennen, wie geht das am besten?

Der naive Weg wäre:

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

aber das scheint nicht sehr effizient zu sein.

Randy Sugianto 'Yuku'
quelle
7
Noch eine Bank
Ivan Black
1
Hinweis: Diese Frage und die meisten Antworten scheinen geschrieben worden zu sein, bevor append()sie in die Sprache kamen, was eine gute Lösung dafür ist. Es wird schnell funktionieren, copy()aber das Slice wird zuerst vergrößert, selbst wenn dies bedeutet, dass ein neues Backing-Array zugewiesen wird, wenn die Kapazität nicht ausreicht. bytes.Buffermacht immer noch Sinn, wenn Sie zusätzliche Komfortmethoden wünschen oder wenn das von Ihnen verwendete Paket dies erwartet.
Thomasrutter
7
Es scheint nicht nur "sehr ineffizient zu sein"; Es gibt ein spezifisches Problem, auf das jeder neue Nicht-CS-Mitarbeiter, den wir jemals bekommen haben, in den ersten Wochen bei der Arbeit gestoßen ist. Es ist quadratisch - O (n * n). Denken Sie an die Zahlenfolge : 1 + 2 + 3 + 4 + .... Es ist n*(n+1)/2die Fläche eines Dreiecks der Basis n. Sie weisen Größe 1, dann Größe 2, dann Größe 3 usw. zu, wenn Sie unveränderliche Zeichenfolgen in einer Schleife anhängen. Dieser quadratische Ressourcenverbrauch manifestiert sich auf mehr als nur diese Weise.
Rob

Antworten:

856

Neuer Weg:

Ab Go 1.10 gibt es einen strings.BuilderTyp, bitte schauen Sie sich diese Antwort für weitere Details an .

Alter Weg:

Verwenden Sie das bytesPaket. Es hat einen BufferTyp, der implementiert 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())
}

Dies geschieht in O (n) Zeit.

Vermarkter
quelle
24
anstelle von println (string (buffer.Bytes ())); Verwendung könnte nur println (buffer.String ())
FigmentEngine
26
Stattdessen buffer := bytes.NewBufferString("")können Sie tun var buffer bytes.Buffer. Sie brauchen auch keines dieser Semikolons :).
crazy2be
66
Unglaublich schnell. Einige naive "+" - Zeichenfolgen in meinem Programm wurden von 3 Minuten auf 1,3 Sekunden verschoben .
Malcolm
10
+1 für "O (n) Zeit"; Ich denke, es ist wichtig, weitere Bemerkungen wie diese zu machen.
widersprach
8
Go 1.10 fügt Strings hinzu. Builder , der wie Bytes ist. Puffer, aber schneller, wenn Ihr Endziel ein String ist.
Josh Bleecher Snyder
272

Die effizienteste Methode zum Verketten von Zeichenfolgen ist die Verwendung der integrierten Funktion copy. In meinen Tests ist dieser Ansatz ~ 3x schneller als die Verwendung bytes.Bufferund viel schneller (~ 12.000x) als die Verwendung des Operators +. Außerdem wird weniger Speicher benötigt.

Ich habe einen Testfall erstellt, um dies zu beweisen. Hier sind die Ergebnisse:

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

Unten finden Sie Code zum Testen:

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)
    }
}
cd1
quelle
6
Der bytes.Buffer sollte im Grunde das Gleiche tun wie die Kopie (mit etwas zusätzlicher Buchhaltung, denke ich) und die Geschwindigkeit ist nicht so unterschiedlich. Also würde ich das benutzen :). Der Unterschied besteht darin, dass der Puffer mit 0 Bytes beginnt und neu zugewiesen werden muss (dies lässt ihn vermutlich etwas langsamer erscheinen). Einfacher zu bedienen.
Aktau
5
buffer.Write(Bytes) ist 30% schneller als buffer.WriteString. [nützlich, wenn Sie die Daten erhalten können als []byte]
Dani-Br
34
Beachten Sie, dass die Benchmark-Ergebnisse verzerrt und nicht authentisch sind. Verschiedene Benchmark-Funktionen werden mit unterschiedlichen Werten von aufgerufen b.N, sodass Sie nicht die Ausführungszeit derselben auszuführenden Aufgabe vergleichen (z. B. kann eine Funktion 1,000Zeichenfolgen anhängen , eine andere kann anhängen, 10,000was einen großen Unterschied im Durchschnitt bewirken kann Zeit von 1 anhängen, BenchmarkConcat()zum Beispiel). Sie sollten in jedem Fall die gleiche Anzahl von Anhängen verwenden (sicherlich nicht b.N) und die gesamte Verkettung innerhalb des Körpers des forBereichs bis b.N( fordh 2 eingebettete Schleifen) durchführen.
icza
18
Darüber hinaus wird der Kopier-Benchmark verzerrt, indem die Zeit, die die Zuweisung benötigt, explizit ignoriert wird, was in den anderen Benchmarks enthalten ist.
Gha.st
6
Darüber hinaus hängt der Kopier-Benchmark davon ab, dass die Länge der resultierenden Zeichenfolge bekannt ist.
Skarllot
227

In Go 1.10+ gibt es strings.Builder, hier .

Ein Builder wird verwendet, um eine Zeichenfolge mithilfe von Schreibmethoden effizient zu erstellen. Es minimiert das Kopieren von Speicher. Der Nullwert ist einsatzbereit.


Beispiel

Es ist fast das gleiche mit bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    // ZERO-VALUE:
    //
    // It's ready to use from the get-go.
    // You don't need to initialize it.
    var str strings.Builder

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

    fmt.Println(str.String())
}

Klicken Sie hier, um dies auf dem Spielplatz zu sehen .


Hinweis

  • Kopieren Sie keinen StringBuilder-Wert, da er die zugrunde liegenden Daten zwischenspeichert.
  • Wenn Sie einen StringBuilder-Wert freigeben möchten, verwenden Sie einen Zeiger darauf.

Unterstützte Schnittstellen

Die Methoden von StringBuilder werden unter Berücksichtigung der vorhandenen Schnittstellen implementiert. Damit Sie einfach in Ihrem Code zum neuen Builder-Typ wechseln können.


Unterschiede zu bytes.Buffer

  • Es kann nur wachsen oder zurückgesetzt werden.

  • Es ist ein copyCheck-Mechanismus integriert, der ein versehentliches Kopieren verhindert:

    func (b *Builder) copyCheck() { ... }

  • In bytes.Bufferkann man auf die zugrunde liegenden Bytes folgendermaßen zugreifen : (*Buffer).Bytes().

    • strings.Builder verhindert dieses Problem.
    • Manchmal ist dies jedoch kein Problem und wird stattdessen gewünscht.
    • Zum Beispiel: Für das Peeking-Verhalten, wenn die Bytes an ein io.Readeretc. übergeben werden.

Weitere Informationen finden Sie hier im Quellcode .

Inanc Gumus
quelle
5
Was meinst du mit "Flucht"? Meinen Sie Escapezeichen in der Zeichenfolge oder nur, dass die zugrunde liegenden Bytes verfügbar gemacht werden können?
Makhdumi
1
@makhdumi Ja, 2. Belichtung der zugrunde liegenden Bytes.
Inanc Gumus
Bemerkenswert strings.Builderimplementiert seine Methoden mit einem Zeigerempfänger, der mich für einen Moment warf. Infolgedessen würde ich wahrscheinlich eine mit erstellen new.
Duncan Jones
@DuncanJones Ich habe jedoch einen Hinweis hinzugefügt, da er hauptsächlich zum Zwischenspeichern von Daten verwendet wird. Es ist normal, einen Zeiger darauf zu verwenden, wenn er über Funktionen usw. geteilt wird. In derselben Funktion können Sie ihn auch als Nichtzeiger verwenden.
Inanc Gumus
130

Das Zeichenfolgenpaket enthält eine Bibliotheksfunktion mit dem Namen Join: http://golang.org/pkg/strings/#Join

Ein Blick auf den Code von Joinzeigt einen ähnlichen Ansatz für die Funktion zum Anhängen, den Kinopiko geschrieben hat: https://golang.org/src/strings/strings.go#L420

Verwendungszweck:

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
mbarkhau
quelle
21
Funktioniert nicht, wenn Sie eine Schleife über etwas ausführen müssen, das keine [] Zeichenfolge ist.
Malcolm
42

Ich habe gerade die oben in meinem eigenen Code angegebene Top-Antwort (ein rekursiver Baumspaziergang) verglichen, und der einfache Concat-Operator ist tatsächlich schneller als der 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()
}

Dies dauerte 0,81 Sekunden, während der folgende Code:

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
} 

dauerte nur 0,61 Sekunden. Dies ist wahrscheinlich auf den Aufwand beim Erstellen des neuen zurückzuführen BufferString.

Update: Ich habe auch die joinFunktion bewertet und sie lief in 0,54 Sekunden.

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,"")
}
JasonMc
quelle
5
Ich glaube, das OP war eher besorgt über die Speicherkomplexität als über die Laufzeitkomplexität, da naive String-Verkettungen jedes Mal zu neuen Speicherzuordnungen führen.
Galaktor
15
Die langsame Geschwindigkeit könnte durchaus mit der Verwendung von fmt.Fprint anstelle vonbuffer.WriteString("\t"); buffer.WriteString(subs[i]);
Robert Jack Will
Ich bin froh zu wissen, dass meine bevorzugte Laufmethode (strings.Join)die schnellste ist, während dieses Sprichwort (bytes.Buffer)der Gewinner ist!
Chetabahana
23

Sie können ein großes Byte-Slice erstellen und die Bytes der kurzen Zeichenfolgen mithilfe von String-Slices darin kopieren. In "Effective Go" gibt es eine Funktion:

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

Wenn die Operationen abgeschlossen sind, verwenden Sie string ( )das große Byte-Slice, um es erneut in eine Zeichenfolge zu konvertieren.


quelle
Es ist interessant, dass es in Go so viele Möglichkeiten gibt, dies zu tun.
Yitzhak
11
Im effektiven Go heißt es auch, dass die Idee so nützlich ist, dass sie in einem eingebauten System erfasst wurde. Sie können also append(slice, byte...)anscheinend Ihre Funktion durch ersetzen .
Aktau
23

Dies ist die schnellste Lösung, bei der Sie nicht zuerst die Gesamtpuffergröße kennen oder berechnen müssen:

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

Nach meinem Benchmark ist es 20% langsamer als die Kopierlösung (8,1 ns pro Anhang anstelle von 6,72 ns), aber immer noch 55% schneller als die Verwendung von bytes.Buffer.

Rog
quelle
23
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}
Harold Ramos
quelle
2
Willkommen bei Stack Overflow! Nehmen Sie sich einen Moment Zeit, um die Bearbeitungshilfe in der Hilfe zu lesen . Die Formatierung beim Stapelüberlauf unterscheidet sich von anderen Websites.
Rizier123
2
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen. Bitte versuchen Sie auch, Ihren Code nicht mit erklärenden Kommentaren zu überfüllen. Dies verringert die Lesbarkeit sowohl des Codes als auch der Erklärungen!
Rizier123
Einfache Lösung 👍
Finn
22

Anmerkung im Jahr 2018 hinzugefügt

Ab Go 1.10 gibt es einen strings.BuilderTyp, bitte schauen Sie sich diese Antwort für weitere Details an .

Antwort vor 201x

Der Benchmark-Code von @ cd1 und andere Antworten sind falsch. b.Nsoll nicht in der Benchmark-Funktion gesetzt werden. Es wird vom Go-Test-Tool dynamisch festgelegt, um festzustellen, ob die Ausführungszeit des Tests stabil ist.

Eine Benchmark-Funktion sollte dieselben Testzeiten ausführen b.Nund der Test innerhalb der Schleife sollte für jede Iteration gleich sein. Also behebe ich es, indem ich eine innere Schleife hinzufüge. Ich füge auch Benchmarks für einige andere Lösungen hinzu:

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

Umgebung ist OS X 10.11.6, 2,2 GHz Intel Core i7

Testergebnisse:

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

Fazit:

  1. CopyPreAllocateist der schnellste Weg; AppendPreAllocateist ziemlich nah an Nr. 1, aber es ist einfacher, den Code zu schreiben.
  2. Concathat eine wirklich schlechte Leistung sowohl für die Geschwindigkeit als auch für die Speichernutzung. Benutze es nicht.
  3. Buffer#Writeund Buffer#WriteStringsind im Grunde gleich schnell, im Gegensatz zu dem, was @ Dani-Br im Kommentar gesagt hat. Wenn stringman bedenkt, dass es tatsächlich []bytein Go ist, macht es Sinn.
  4. bytes.Buffer verwenden grundsätzlich die gleiche Lösung wie Copybei zusätzlicher Buchhaltung und anderen Dingen.
  5. Copyund Appendverwenden Sie eine Bootstrap-Größe von 64, die mit bytes.Buffer identisch ist
  6. AppendVerwenden Sie mehr Speicher und Zuweisungen. Ich denke, dies hängt mit dem verwendeten Wachstumsalgorithmus zusammen. Der Speicher wächst nicht so schnell wie Bytes

Vorschlag:

  1. Für einfache Aufgaben wie das, was OP will, würde ich Appendoder verwenden AppendPreAllocate. Es ist schnell genug und einfach zu bedienen.
  2. Wenn Sie den Puffer gleichzeitig lesen und schreiben müssen, verwenden Sie bytes.Buffernatürlich. Dafür ist es konzipiert.
PickBoy
quelle
13

Mein ursprünglicher Vorschlag war

s12 := fmt.Sprint(s1,s2)

Aber obige Antwort mit Bytes.Buffer - WriteString () ist der effizienteste Weg.

Mein erster Vorschlag verwendet Reflexion und einen Typschalter. Siehe (p *pp) doPrintund(p *pp) printArg
Es gibt keine universelle Stringer () - Schnittstelle für Basistypen, wie ich naiv gedacht hatte.

Zumindest verwendet Sprint () intern einen Bytes.Buffer. Somit

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

ist in Bezug auf Speicherzuordnungen akzeptabel.

=> Die Sprint () - Verkettung kann für eine schnelle Debug-Ausgabe verwendet werden.
=> Andernfalls verwenden Sie bytes.Buffer ... WriteString

Peter Buchmann
quelle
8
Es ist nicht eingebaut und nicht effizient.
PeterSO
Das Importieren eines Pakets (wie fmt) bedeutet, dass es nicht integriert ist. Es ist in der Standardbibliothek.
Malcolm
Es ist nur langsam, weil es Reflexion über seine Argumente verwendet. Es ist effizient. Ansonsten ist es nicht weniger effizient als das Verbinden mit
Strings. Join
11

Erweitern der Antwort von cd1: Sie können append () anstelle von copy () verwenden. append () macht immer größere Vorkehrungen, kostet etwas mehr Speicher, spart aber Zeit. Ich habe oben zwei weitere Benchmarks hinzugefügt . Lokal ausführen mit

go test -bench=. -benchtime=100ms

Auf meinem Thinkpad T400 ergibt sich:

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op
Peter Buchmann
quelle
4

Dies ist die aktuelle Version des von @ cd1 ( Go 1.8, linux x86_64) bereitgestellten Benchmarks mit den von @icza und @PickBoy erwähnten Fehlerkorrekturen.

Bytes.Bufferist nur 7mal schneller als die direkte Verkettung von Zeichenfolgen über den +Operator.

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

Timings:

BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op
Vitaly Isaev
quelle
Ich denke nicht, dass die manuelle Einstellung von bN der richtige Weg ist, um Benchmark-Funktionen des
Testpakets
@ PickBoy, bitte begründen Sie Ihren Standpunkt. Warum ist Ihrer Meinung b.Nnach eine öffentliche Variable?
Vitaly Isaev
1
bN soll nicht in der Benchmark-Funktion eingestellt sein. Es wird vom Go-Test-Tool dynamisch eingestellt. Eine Benchmark-Funktion sollte die gleichen bN-Testzeiten ausführen, aber in Ihrem Code (sowie im Code von @ cd1) ist jeder Test in der Schleife ein anderer Test (da die Länge der Zeichenfolge zunimmt)
PickBoy
@PickBoy, wenn Sie das Go-Test-Tool b.Ndynamisch einstellen lassen , erhalten Sie in verschiedenen Testfällen eine Zeichenfolge unterschiedlicher Länge. Siehe Kommentar
Vitaly Isaev
Aus diesem Grund sollten Sie innerhalb der bN-Schleife eine innere Schleife mit einer festen Anzahl von Iterationen wie 10000 hinzufügen.
PickBoy
3

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()
}
Xian Shu
quelle
1

Ich mache es mit folgendem: -

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
Krish Bhanushali
quelle
Dies behebt nicht das Problem von OP, einen String durch eine Reihe von Iterationen mit einer for-Schleife zu erstellen.
Codeforester
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))
}
Rajni Kant
quelle
3
Bitte posten Sie nicht nur Antworten. Bitte erläutern Sie, was dieser Code bewirkt und warum er die Lösung ist.
Korashen
-1

Benchmark-Ergebnis mit Speicherzuordnungsstatistik. Überprüfen Sie den Benchmark-Code bei Github .

Verwenden Sie Strings.Builder, um die Leistung zu optimieren.

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
hechen0
quelle
Bitte geben Sie @ cd1 für die ursprünglichen Testfälle, auf denen Sie hier aufbauen, eine Gutschrift.
colm.anseo
-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
user2288856
quelle
5
Dies ist eine sehr langsame Lösung, da sie Reflektion verwendet, die Formatzeichenfolge analysiert und eine Kopie der Daten für die []byte(s1)Konvertierung erstellt. Können Sie im Vergleich zu anderen veröffentlichten Lösungen einen einzigen Vorteil Ihrer Lösung nennen?
Punkte
-5

strings.Join() aus dem "Strings" -Paket

Wenn Sie eine Typinkongruenz haben (z. B. wenn Sie versuchen, ein int und eine Zeichenfolge zu verbinden), führen Sie RANDOMTYPE aus (was Sie ändern möchten).

EX:

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

Ausgabe :

hello all you people in here
Liam
quelle
4
Dieser Code wird nicht einmal kompiliert: Er strings.Join()akzeptiert nur zwei Parameter: ein Slice und ein Trennzeichen string.
icza
das kann nicht helfen
Anshu
Fügen Sie hier einige Änderungen hinzu.
Anshu