996 Stimmen

Wie man Strings in Go effizient verketten kann

Im Go ist ein string ein primitiver Typ, was bedeutet, dass er schreibgeschützt ist und jede Manipulation einen neuen String erstellt.

Also, wenn ich Strings viele Male hintereinander verketten möchte, ohne die Länge des resultierenden Strings zu kennen, wie mache ich das am besten?

Der naive Ansatz wäre:

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

aber das scheint nicht sehr effizient zu sein.

12 Stimmen

Noch eine Bank

1 Stimmen

Hinweis: Diese Frage und die meisten Antworten scheinen vor dem Hinzufügen von append() in die Sprache geschrieben worden zu sein, was eine gute Lösung dafür ist. Es wird genauso schnell wie copy() ausgeführt, aber das Slice wird zuerst erweitert, auch wenn dies bedeutet, dass ein neues zugrundeliegendes Array allokiert wird, wenn die Kapazität nicht ausreicht. bytes.Buffer macht immer noch Sinn, wenn Sie seine zusätzlichen Komfortmethoden möchten oder wenn das Paket, das Sie verwenden, dies erwartet.

10 Stimmen

Es ist nicht nur "sehr ineffizient"; es hat ein spezifisches Problem, mit dem jeder neue Nicht-CS-Mitarbeiter, den wir jemals eingestellt haben, in den ersten Wochen der Arbeit konfrontiert wird. Es ist quadratisch - O(n*n). Denken Sie an die Zahlenfolge: 1 + 2 + 3 + 4 + .... Es ist n*(n+1)/2, die Fläche eines Dreiecks mit der Basis n. Sie reservieren die Größe 1, dann die Größe 2, dann die Größe 3 usw., wenn Sie unveränderliche Strings in einer Schleife anhängen. Diese quadratische Ressourcenverwendung äußert sich auf mehrere Weisen als nur diese.

11voto

Peter Buchmann Punkte 514

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

go test -bench=. -benchtime=100ms

Auf meinem Thinkpad T400s ergibt sich folgendes:

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

4voto

Vitaly Isaev Punkte 5040

Dies ist die aktuelle Version des Benchmarks von @cd1 (Go 1.8, linux x86_64) mit den behobenen Fehlern, die von @icza und @PickBoy erwähnt wurden.

Bytes.Buffer ist nur 7 Mal schneller als die direkte Zeichenkettenverkettung ü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")
        }
    }
}

Zeiten:

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

0 Stimmen

Ich denke nicht, dass das manuelle Festlegen von b.N der richtige Weg ist, um Benchmark-Funktionen des Testpakets zu verwenden.

0 Stimmen

@PickBoy, bitte rechtfertigen Sie Ihren Standpunkt. Warum denken Sie, dass b.N eine öffentliche Variable ist?

1 Stimmen

B.N soll nicht in der Benchmark-Funktion festgelegt werden. Es wird dynamisch vom go test-Tool gesetzt. Eine Benchmark-Funktion sollte den gleichen Test b.N Mal durchführen, aber in Ihrem Code (wie auch im Code von @cd1) ist jeder Test in der Schleife ein anderer Test (weil die Länge des Strings wächst)

4voto

Xian Shu Punkte 632

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

1voto

Krish Bhanushali Punkte 140

Ich mache es mit folgendem Code: -

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //wo der zweite Parameter ein Trennzeichen ist. 
    fmt.Println(concatenation) //abc
}

0 Stimmen

Dies behandelt nicht das Problem des Fragestellers (OP), eine Zeichenkette durch eine Reihe von Iterationen mit einer for-Schleife zu erstellen.

0 Stimmen

Nun @codeforester, du müsstest zuerst die 'list' als ein Array von Zeichenfolgen parsen.

0voto

rajni kant Punkte 96
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))
}

4 Stimmen

Bitte geben Sie keine Antworten nur mit Code ein. Bitte geben Sie eine Erklärung ab, was dieser Code macht und warum er die Lösung ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X