In Go ist a string
ein 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.
string
go
string-concatenation
Randy Sugianto 'Yuku'
quelle
quelle
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.Buffer
macht immer noch Sinn, wenn Sie zusätzliche Komfortmethoden wünschen oder wenn das von Ihnen verwendete Paket dies erwartet.1 + 2 + 3 + 4 + ...
. Es istn*(n+1)/2
die Fläche eines Dreiecks der Basisn
. 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.Antworten:
Neuer Weg:
Ab Go 1.10 gibt es einen
strings.Builder
Typ, bitte schauen Sie sich diese Antwort für weitere Details an .Alter Weg:
Verwenden Sie das
bytes
Paket. Es hat einenBuffer
Typ, der implementiertio.Writer
.Dies geschieht in O (n) Zeit.
quelle
buffer := bytes.NewBufferString("")
können Sie tunvar buffer bytes.Buffer
. Sie brauchen auch keines dieser Semikolons :).Die effizienteste Methode zum Verketten von Zeichenfolgen ist die Verwendung der integrierten Funktion
copy
. In meinen Tests ist dieser Ansatz ~ 3x schneller als die Verwendungbytes.Buffer
und 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:
Unten finden Sie Code zum Testen:
quelle
buffer.Write
(Bytes) ist 30% schneller alsbuffer.WriteString
. [nützlich, wenn Sie die Daten erhalten können als[]byte
]b.N
, sodass Sie nicht die Ausführungszeit derselben auszuführenden Aufgabe vergleichen (z. B. kann eine Funktion1,000
Zeichenfolgen anhängen , eine andere kann anhängen,10,000
was 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 nichtb.N
) und die gesamte Verkettung innerhalb des Körpers desfor
Bereichs bisb.N
(for
dh 2 eingebettete Schleifen) durchführen.In Go 1.10+ gibt es
strings.Builder
, hier .Beispiel
Es ist fast das gleiche mit
bytes.Buffer
.Klicken Sie hier, um dies auf dem Spielplatz zu sehen .
Hinweis
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.Buffer
kann man auf die zugrunde liegenden Bytes folgendermaßen zugreifen :(*Buffer).Bytes()
.strings.Builder
verhindert dieses Problem.io.Reader
etc. übergeben werden.Weitere Informationen finden Sie hier im Quellcode .
quelle
strings.Builder
implementiert seine Methoden mit einem Zeigerempfänger, der mich für einen Moment warf. Infolgedessen würde ich wahrscheinlich eine mit erstellennew
.Das Zeichenfolgenpaket enthält eine Bibliotheksfunktion mit dem Namen
Join
: http://golang.org/pkg/strings/#JoinEin Blick auf den Code von
Join
zeigt einen ähnlichen Ansatz für die Funktion zum Anhängen, den Kinopiko geschrieben hat: https://golang.org/src/strings/strings.go#L420Verwendungszweck:
quelle
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
.Dies dauerte 0,81 Sekunden, während der folgende Code:
dauerte nur 0,61 Sekunden. Dies ist wahrscheinlich auf den Aufwand beim Erstellen des neuen zurückzuführen
BufferString
.Update: Ich habe auch die
join
Funktion bewertet und sie lief in 0,54 Sekunden.quelle
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
die schnellste ist, während dieses Sprichwort(bytes.Buffer)
der Gewinner ist!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:
Wenn die Operationen abgeschlossen sind, verwenden Sie
string ( )
das große Byte-Slice, um es erneut in eine Zeichenfolge zu konvertieren.quelle
append(slice, byte...)
anscheinend Ihre Funktion durch ersetzen .Dies ist die schnellste Lösung, bei der Sie nicht zuerst die Gesamtpuffergröße kennen oder berechnen müssen:
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.
quelle
quelle
Anmerkung im Jahr 2018 hinzugefügt
Ab Go 1.10 gibt es einen
strings.Builder
Typ, 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.N
soll 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.N
und 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:Umgebung ist OS X 10.11.6, 2,2 GHz Intel Core i7
Testergebnisse:
Fazit:
CopyPreAllocate
ist der schnellste Weg;AppendPreAllocate
ist ziemlich nah an Nr. 1, aber es ist einfacher, den Code zu schreiben.Concat
hat eine wirklich schlechte Leistung sowohl für die Geschwindigkeit als auch für die Speichernutzung. Benutze es nicht.Buffer#Write
undBuffer#WriteString
sind im Grunde gleich schnell, im Gegensatz zu dem, was @ Dani-Br im Kommentar gesagt hat. Wennstring
man bedenkt, dass es tatsächlich[]byte
in Go ist, macht es Sinn.Copy
bei zusätzlicher Buchhaltung und anderen Dingen.Copy
undAppend
verwenden Sie eine Bootstrap-Größe von 64, die mit bytes.Buffer identisch istAppend
Verwenden Sie mehr Speicher und Zuweisungen. Ich denke, dies hängt mit dem verwendeten Wachstumsalgorithmus zusammen. Der Speicher wächst nicht so schnell wie BytesVorschlag:
Append
oder verwendenAppendPreAllocate
. Es ist schnell genug und einfach zu bedienen.bytes.Buffer
natürlich. Dafür ist es konzipiert.quelle
Mein ursprünglicher Vorschlag war
Aber obige Antwort mit Bytes.Buffer - WriteString () ist der effizienteste Weg.
Mein erster Vorschlag verwendet Reflexion und einen Typschalter. Siehe
(p *pp) doPrint
und(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
ist in Bezug auf Speicherzuordnungen akzeptabel.
=> Die Sprint () - Verkettung kann für eine schnelle Debug-Ausgabe verwendet werden.
=> Andernfalls verwenden Sie bytes.Buffer ... WriteString
quelle
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
Auf meinem Thinkpad T400 ergibt sich:
quelle
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.Buffer
ist nur7
mal schneller als die direkte Verkettung von Zeichenfolgen über den+
Operator.Timings:
quelle
b.N
nach eine öffentliche Variable?b.N
dynamisch einstellen lassen , erhalten Sie in verschiedenen Testfällen eine Zeichenfolge unterschiedlicher Länge. Siehe Kommentargoutils.JoinBetween
quelle
Ich mache es mit folgendem: -
quelle
quelle
Benchmark-Ergebnis mit Speicherzuordnungsstatistik. Überprüfen Sie den Benchmark-Code bei Github .
Verwenden Sie Strings.Builder, um die Leistung zu optimieren.
quelle
quelle
[]byte(s1)
Konvertierung erstellt. Können Sie im Vergleich zu anderen veröffentlichten Lösungen einen einzigen Vorteil Ihrer Lösung nennen?strings.Join()
aus dem "Strings" -PaketWenn 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:
Ausgabe :
quelle
strings.Join()
akzeptiert nur zwei Parameter: ein Slice und ein Trennzeichenstring
.