Comment concaténer efficacement des chaînes dans go


727

Dans Go, a stringest un type primitif, ce qui signifie qu'il est en lecture seule, et chaque manipulation de celui-ci créera une nouvelle chaîne.

Donc, si je veux concaténer des chaînes plusieurs fois sans connaître la longueur de la chaîne résultante, quelle est la meilleure façon de le faire?

La voie naïve serait:

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

mais cela ne semble pas très efficace.



1
Remarque: Cette question et la plupart des réponses semblent avoir été écrites avant d'être append()entrées dans la langue, ce qui est une bonne solution pour cela. Il fonctionnera rapidement comme copy()mais augmentera la tranche en premier, même si cela signifie allouer une nouvelle matrice de sauvegarde si la capacité n'est pas suffisante. bytes.Bufferest toujours logique si vous voulez ses méthodes supplémentaires ou si le package que vous utilisez l'attend.
thomasrutter

7
Cela ne fait pas que "sembler très inefficace"; il y a un problème spécifique que chaque nouvelle embauche non-CS que nous avons jamais rencontrée se produit au cours des premières semaines de travail. C'est quadratique - O (n * n). Pensez à la séquence de nombres: 1 + 2 + 3 + 4 + .... C'est n*(n+1)/2l'aire d'un triangle de base n. Vous allouez la taille 1, puis la taille 2, puis la taille 3, etc. lorsque vous ajoutez des chaînes immuables dans une boucle. Cette consommation quadratique des ressources se manifeste de plus de façons que cela.
Rob

Réponses:


856

Nouvelle façon:

À partir de Go 1.10, il existe un strings.Buildertype, veuillez jeter un œil à cette réponse pour plus de détails .

Old Way:

Utilisez le bytespackage. Il a un Buffertype qui implémente 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())
}

Cela le fait en temps O (n).


24
au lieu de println (string (buffer.Bytes ())); utilisation pourrait simplement faire println (buffer.String ())
FigmentEngine

26
Au lieu de cela buffer := bytes.NewBufferString(""), vous pouvez le faire var buffer bytes.Buffer. Vous n'avez également besoin d'aucun de ces points-virgules :).
crazy2be

66
Incroyablement rapide. J'ai fait passer une chaîne de naïfs "+" dans mon programme de 3 minutes à 1,3 seconde .
Malcolm

10
+1 pour "O (n) time"; Je pense qu'il est important de faire plus de remarques comme celle-ci.
contredit

8
Go 1.10 ajoute strings.Builder , qui est comme bytes.Buffer mais plus rapide lorsque votre objectif final est une chaîne.
Josh Bleecher Snyder

272

La façon la plus efficace de concaténer des chaînes est d'utiliser la fonction intégrée copy. Dans mes tests, cette approche est ~ 3x plus rapide que l'utilisation bytes.Bufferet beaucoup plus rapide (~ 12 000x) que l'utilisation de l'opérateur +. En outre, il utilise moins de mémoire.

J'ai créé un cas de test pour le prouver et voici les résultats:

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

Voici le code pour les tests:

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

6
Le bytes.Buffer devrait faire essentiellement la même chose que la copie (avec une comptabilité supplémentaire je suppose) et la vitesse n'est pas si différente. J'utiliserais donc ça :). La différence étant que le tampon commence par 0 octet, il doit donc être réaffecté (cela semble plus lent, je suppose). Plus facile à utiliser, cependant.
Aktau

5
buffer.Write(octets) est 30% plus rapide que buffer.WriteString. [utile si vous pouvez obtenir les données []byte]
Dani-Br

34
Notez que les résultats de référence sont déformés et ne sont pas authentiques. Différentes fonctions de référence seront appelées avec différentes valeurs de b.N, et vous ne comparez donc pas le temps d'exécution de la même tâche à effectuer (par exemple, une fonction peut ajouter des 1,000chaînes, une autre peut ajouter, 10,000ce qui peut faire une grande différence dans la moyenne temps de 1 ajout, BenchmarkConcat()par exemple). Vous devez utiliser le même nombre d'ajouts dans chaque cas (certainement pas b.N) et faire toute la concaténation à l'intérieur du corps de la forplage b.N(c'est-à-dire, 2 forboucles intégrées).
icza

18
En outre, le benchmark de copie est biaisé en ignorant explicitement le temps que prend l'allocation, qui est inclus dans les autres benchmarks.
gha.st

6
De plus, le benchmark de copie repose sur la connaissance de la longueur de la chaîne résultante.
Skarllot

227

Dans Go 1.10+, il y strings.Builderen a ici .

Un générateur est utilisé pour créer efficacement une chaîne à l'aide de méthodes d'écriture. Il minimise la copie de la mémoire. La valeur zéro est prête à l'emploi.


Exemple

C'est presque la même chose avec 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())
}

Cliquez pour voir ceci sur le terrain de jeu .


Remarque

  • Ne copiez pas une valeur StringBuilder car elle met en cache les données sous-jacentes.
  • Si vous souhaitez partager une valeur StringBuilder, utilisez un pointeur vers celle-ci.

Interfaces prises en charge

Les méthodes de StringBuilder sont mises en œuvre avec les interfaces existantes à l'esprit. Pour que vous puissiez basculer facilement vers le nouveau type Builder dans votre code.


Différences par rapport aux octets.Buffer

  • Il ne peut que croître ou se réinitialiser.

  • Il a un mécanisme copyCheck intégré qui empêche de le copier accidentellement:

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

  • Dans bytes.Buffer, on peut accéder aux octets sous - jacents comme ceci: (*Buffer).Bytes().

    • strings.Builder empêche ce problème.
    • Parfois, ce n'est pas un problème et souhaité à la place.
    • Par exemple: pour le comportement furtif lorsque les octets sont passés à un io.Readeretc.

Consultez son code source pour plus de détails, ici .


5
Qu'entendez-vous par «évasion»? Voulez-vous dire des échappements dans la chaîne, ou simplement que les octets sous-jacents peuvent être exposés?
makhdumi

1
@makhdumi Oui, 2e, exposition des octets sous-jacents.
Inanc Gumus

À noter strings.Buildermet en œuvre ses méthodes à l'aide d'un récepteur de pointeur, ce qui m'a jeté un moment. En conséquence, j'en créerais probablement un en utilisant new.
Duncan Jones

@DuncanJones J'ai toutefois ajouté une note, car il est principalement utilisé pour la mise en cache des données, il est normal d'utiliser un pointeur sur celui-ci lors du partage entre des fonctions, etc. Dans la même fonction, vous pouvez également l'utiliser comme non-pointeur.
Inanc Gumus

130

Il y a une fonction de bibliothèque dans le package de chaînes appelée Join: http://golang.org/pkg/strings/#Join

Un regard sur le code de Joinmontre une approche similaire à la fonction Append Kinopiko a écrit: https://golang.org/src/strings/strings.go#L420

Usage:

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

21
Ne fonctionne pas lorsque vous devez boucler sur quelque chose qui n'est pas une chaîne [].
Malcolm

42

Je viens de comparer la première réponse publiée ci-dessus dans mon propre code (une promenade récursive dans l'arbre) et l'opérateur de concaténation simple est en fait plus rapide que le 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()
}

Cela a pris 0,81 seconde, alors que le code suivant:

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
} 

n'a pris que 0,61 secondes. Cela est probablement dû aux frais généraux liés à la création du nouveau BufferString.

Mise à jour: j'ai également testé la joinfonction et elle a fonctionné en 0,54 seconde.

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

5
Je pense que l'OP était plus préoccupé par la complexité de la mémoire que par la complexité d'exécution, étant donné que les concaténations de chaînes naïves entraînent à chaque fois de nouvelles allocations de mémoire.
galaktor

15
La vitesse lente de ceci pourrait bien être liée à l'utilisation de fmt.Fprint au lieu de buffer.WriteString("\t"); buffer.WriteString(subs[i]);
Robert Jack Will

Je suis heureux de savoir que ma méthode de (strings.Join)course préférée est la plus rapide alors que, d'après ce dicton, c'est (bytes.Buffer)le gagnant!
Chetabahana

23

Vous pouvez créer une grande tranche d'octets et y copier les octets des chaînes courtes à l'aide de tranches de chaîne. Il y a une fonction donnée dans "Go efficace":

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

Ensuite, lorsque les opérations sont terminées, utilisez string ( )sur la grande tranche d'octets pour la convertir à nouveau en chaîne.


Il est intéressant de noter qu'il existe de nombreuses façons de le faire dans Go.
Yitzhak

11
Dans go efficace, il dit également que l'idée est si utile qu'elle a été capturée dans une fonction intégrée. Vous pouvez donc remplacer votre fonction par append(slice, byte...), semble-t-il.
Aktau

23

Il s'agit de la solution la plus rapide qui ne nécessite pas que vous connaissiez ou calculiez d'abord la taille globale du tampon:

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

D'après mes tests , c'est 20% plus lent que la solution de copie (8,1 ns par ajout plutôt que 6,72 ns) mais toujours 55% plus rapide que l'utilisation de bytes.Buffer.


23
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}

2
Bienvenue dans Stack Overflow! Prenez un moment pour lire l' aide à l' édition dans le centre d'aide. Le formatage sur Stack Overflow est différent de celui des autres sites.
Rizier123

2
Bien que cet extrait de code puisse résoudre la question, y compris une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question pour les lecteurs à l'avenir, et ces personnes pourraient ne pas connaître les raisons de votre suggestion de code. Essayez également de ne pas surcharger votre code avec des commentaires explicatifs, cela réduit la lisibilité du code et des explications!
Rizier123

Solution simple 👍
Finn

22

Note ajoutée en 2018

À partir de Go 1.10, il existe un strings.Buildertype, veuillez jeter un œil à cette réponse pour plus de détails .

Réponse pré-201x

Le code de référence de @ cd1 et d'autres réponses sont erronés. b.Nn'est pas censé être défini dans la fonction de référence. Il est défini dynamiquement par l'outil de test go pour déterminer si le temps d'exécution du test est stable.

Une fonction de référence doit exécuter les mêmes b.Ntemps de test et le test à l'intérieur de la boucle doit être le même pour chaque itération. Je le corrige donc en ajoutant une boucle intérieure. J'ajoute également des repères pour d'autres solutions:

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

L'environnement est OS X 10.11.6, 2,2 GHz Intel Core i7

Résultats de test:

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

Conclusion:

  1. CopyPreAllocateest le moyen le plus rapide; AppendPreAllocateest assez proche de No.1, mais il est plus facile d'écrire le code.
  2. Concata de très mauvaises performances à la fois pour la vitesse et l'utilisation de la mémoire. Ne l'utilisez pas.
  3. Buffer#Writeet Buffer#WriteStringsont fondamentalement les mêmes en vitesse, contrairement à ce que @ Dani-Br a dit dans le commentaire. Considérant que stringc'est bien []bytedans Go, ça a du sens.
  4. bytes.Buffer utilise essentiellement la même solution Copyqu'avec la tenue de livres supplémentaires et d'autres choses.
  5. Copyet Appendutilisez une taille de bootstrap de 64, la même que celle des octets.
  6. Appendutiliser plus de mémoire et d'allocations, je pense que c'est lié à l'algorithme de croissance qu'il utilise. Cela n'augmente pas la mémoire aussi vite que les octets.

Suggestion:

  1. Pour une tâche simple telle que ce que veut OP, j'utiliserais Appendou AppendPreAllocate. C'est assez rapide et facile à utiliser.
  2. Si vous avez besoin de lire et d'écrire le tampon en même temps, utilisez bytes.Bufferbien sûr. C'est pour ça qu'il est conçu.

13

Ma suggestion initiale était

s12 := fmt.Sprint(s1,s2)

Mais la réponse ci-dessus utilise bytes.Buffer - WriteString () est le moyen le plus efficace.

Ma suggestion initiale utilise la réflexion et un commutateur de type. Voir (p *pp) doPrintet(p *pp) printArg
Il n'y a pas d'interface universelle Stringer () pour les types de base, comme je l'avais naïvement pensé.

Au moins cependant, Sprint () utilise en interne un octet.Buffer. Donc

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

est acceptable en termes d'allocations de mémoire.

=> La concaténation Sprint () peut être utilisée pour une sortie de débogage rapide.
=> Sinon, utilisez bytes.Buffer ... WriteString


8
Ce n'est pas intégré et ce n'est pas efficace.
peterSO

L'importation d'un package (comme fmt) signifie qu'il n'est pas intégré. C'est dans la bibliothèque standard.
Malcolm

C'est lent seulement parce qu'il utilise la réflexion sur ses arguments. C'est efficace. Sinon, ce n'est pas moins efficace que de se joindre à des
chaînes.Join

11

Extension de la réponse de cd1: vous pouvez utiliser append () au lieu de copy (). append () fait des provisions d'avance toujours plus grandes, coûtant un peu plus de mémoire, mais économisant du temps. J'ai ajouté deux autres repères en haut de la vôtre. Exécuter localement avec

go test -bench=. -benchtime=100ms

Sur mon thinkpad T400s ça donne:

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

4

Il s'agit de la version actuelle de benchmark fournie par @ cd1 ( Go 1.8, linux x86_64) avec les correctifs de bugs mentionnés par @icza et @PickBoy.

Bytes.Bufferest seulement 7plus rapide que la concaténation directe de chaînes via l' +opérateur.

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

Calendrier:

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

Je ne pense pas que le réglage manuel de bN soit la bonne façon d'utiliser les fonctions de référence du package de test
PickBoy

@PickBoy, veuillez justifier votre point de vue. Pourquoi pensez-vous que b.Nc'est une variable publique?
Vitaly Isaev

1
bN n'est pas censé être défini dans la fonction de référence. Il est défini dynamiquement par l'outil de test go. Une fonction de référence devrait exécuter le même test bN fois, mais dans votre code (ainsi que le code de @ cd1), chaque test de la boucle est un test différent (car la longueur de la chaîne augmente)
PickBoy

@PickBoy, si vous laissez l'outil de test go défini b.Ndynamiquement, vous vous retrouverez avec des chaînes de longueur différente dans différents cas de test. Voir le commentaire
Vitaly Isaev

C'est pourquoi vous devez ajouter une boucle interne d'un nombre fixe d'itérations, comme 10000, à l'intérieur de la boucle bN.
PickBoy

3

goutils.

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

1

Je le fais en utilisant ce qui suit: -

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}

Cela ne résout pas le problème de l'OP de construire une chaîne à travers une série d'itérations, avec une boucle for.
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))
}

3
Veuillez ne pas poster uniquement les réponses. Veuillez expliquer ce que fait ce code et pourquoi c'est la solution.
Korashen

-1

résultat de référence avec statistiques d'allocation de mémoire. vérifier le code de référence sur github .

utilisez strings.Builder pour optimiser les performances.

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

merci de donner à @ cd1 les cas de test originaux sur lesquels vous construisez ici.
colm.anseo

-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))

5
C'est une solution très lente, car elle utilise la réflexion, elle analyse la chaîne de format et elle fait une copie des données pour la []byte(s1)conversion. En le comparant à d'autres solutions publiées, pouvez-vous citer un seul avantage de votre solution?
pts

-5

strings.Join() du package "strings"

Si vous avez une incompatibilité de type (comme si vous essayez de joindre un int et une chaîne), vous faites RANDOMTYPE (chose que vous voulez changer)

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

Production :

hello all you people in here

4
Ce code ne compile même pas: strings.Join()ne prend que 2 paramètres: une tranche et un séparateur string.
icza

cela ne peut pas aider
Anshu

ajoutez quelques modifications ici.
Anshu
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.