Cómo concatenar cadenas de manera eficiente en go

727

En Go, a stringes un tipo primitivo, lo que significa que es de solo lectura, y cada manipulación creará una nueva cadena.

Entonces, si quiero concatenar cadenas muchas veces sin saber la longitud de la cadena resultante, ¿cuál es la mejor manera de hacerlo?

La forma ingenua sería:

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

pero eso no parece muy eficiente.

Randy Sugianto 'Yuku'
fuente
77
Un banco
Ivan Black
1
Nota: Esta pregunta y la mayoría de las respuestas parecen haber sido escritas antes de append()entrar al idioma, lo cual es una buena solución para esto. Funcionará rápidamente, copy()pero crecerá el segmento primero, incluso si eso significa asignar una nueva matriz de respaldo si la capacidad no es suficiente. bytes.Bufferaún tiene sentido si desea sus métodos de conveniencia adicionales o si el paquete que está utilizando lo espera.
thomasrutter
77
No solo "parece muy ineficiente"; tiene un problema específico con el que cada nuevo empleado que no es CS se ha topado en las primeras semanas en el trabajo. Es cuadrático - O (n * n). Piense en la secuencia de números: 1 + 2 + 3 + 4 + .... Es n*(n+1)/2, el área de un triángulo de base n. Usted asigna el tamaño 1, luego el tamaño 2, luego el tamaño 3, etc. cuando agrega cadenas inmutables en un bucle. Este consumo de recursos cuadráticos se manifiesta en más formas que solo esto.
Rob

Respuestas:

856

Nueva manera:

De Go 1.10 hay un strings.Buildertipo, eche un vistazo a esta respuesta para obtener más detalles .

Vieja forma:

Usa el bytespaquete. Tiene un Buffertipo que implementa 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())
}

Esto lo hace en O (n) tiempo.

vendedor
fuente
24
en lugar de println (string (buffer.Bytes ())); use podría hacer println (buffer.String ())
FigmentEngine
26
En lugar de buffer := bytes.NewBufferString(""), puedes hacerlo var buffer bytes.Buffer. Tampoco necesitas ninguno de esos puntos y comas :).
crazy2be
66
Increíblemente rápido. Hice un ingenioso concat de cadena "+" en mi programa de 3 minutos a 1.3 segundos .
Malcolm
10
+1 para "O (n) tiempo"; Creo que es importante hacer más comentarios como este.
contradice
8
Go 1.10 agrega strings.Builder , que es como bytes.Buffer pero más rápido cuando su objetivo final es una cadena.
Josh Bleecher Snyder
272

La forma más eficiente de concatenar cadenas es usar la función incorporada copy. En mis pruebas, ese enfoque es ~ 3 veces más rápido que usarlo bytes.Buffery mucho más rápido (~ 12,000x) que usar el operador +. Además, usa menos memoria.

He creado un caso de prueba para probar esto y aquí están los resultados:

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

A continuación se muestra el código para la prueba:

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
fuente
66
Los bytes. El amortiguador debería hacer básicamente lo mismo que la copia (con un poco de contabilidad adicional, supongo) y la velocidad no es tan diferente. Entonces usaría eso :). La diferencia es que el búfer comienza con 0 bytes, por lo que tiene que reasignarse (esto hace que parezca un poco más lento, supongo). Sin embargo, es más fácil de usar.
Aktau
55
buffer.Write(bytes) es un 30% más rápido que buffer.WriteString. [útil si puede obtener los datos como []byte]
Dani-Br
34
Tenga en cuenta que los resultados de referencia están distorsionados y no son auténticos. Se llamarán diferentes funciones de referencia con diferentes valores de b.N, por lo que no está comparando el tiempo de ejecución de la misma tarea a realizar (por ejemplo, una función puede agregar 1,000cadenas, otra puede agregar, lo 10,000que puede hacer una gran diferencia en el promedio tiempo de 1 agregar, BenchmarkConcat()por ejemplo). Debe usar el mismo recuento de anexos en cada caso (ciertamente no b.N), y hacer toda la concatenación dentro del cuerpo del forrango a b.N(es decir, 2 forbucles incrustados).
icza
18
Además, el punto de referencia de la copia está sesgado al ignorar explícitamente el tiempo que lleva la asignación, que se incluye en los otros puntos de referencia.
gha.st
66
Además, el punto de referencia de copia se basa en conocer la longitud de la cadena resultante.
Skarllot
227

En Go 1.10+ hay strings.Builder, aquí .

Un generador se usa para construir eficientemente una cadena usando métodos de escritura. Minimiza la copia de memoria. El valor cero está listo para usar.


Ejemplo

Es casi lo mismo con 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())
}

Haga clic para ver esto en el patio de recreo .


Nota

  • No copie un valor de StringBuilder ya que almacena en caché los datos subyacentes.
  • Si desea compartir un valor de StringBuilder, use un puntero a él.

Interfaces soportadas

Los métodos de StringBuilder se implementan teniendo en cuenta las interfaces existentes. Para que pueda cambiar al nuevo tipo de generador fácilmente en su código.


Diferencias de bytes.

  • Solo puede crecer o restablecerse.

  • Tiene un mecanismo copyCheck incorporado que evita copiarlo de manera accidental:

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

  • En bytes.Buffer, se puede acceder a los bytes subyacentes como este: (*Buffer).Bytes().

    • strings.Builder Previene este problema.
    • A veces, esto no es un problema y se desea en su lugar.
    • Por ejemplo: para el comportamiento de espiar cuando los bytes se pasan a un io.Readeretc.

Mira su código fuente para más detalles, aquí .

Inanc Gumus
fuente
55
¿Qué quieres decir con 'escape'? ¿Quiere decir escapes en la cadena, o simplemente que los bytes subyacentes pueden estar expuestos?
makhdumi
1
@makhdumi Sí, segundo, exposición de bytes subyacentes.
Inanc Gumus
Vale la pena señalar que strings.Builderimplementa sus métodos utilizando un receptor de puntero, que me arrojó por un momento. Como resultado, probablemente crearía uno usando new.
Duncan Jones
@DuncanJones Sin embargo, he agregado una nota, ya que se usa principalmente para almacenar datos en caché, es normal usar un puntero al compartirlo entre funciones, etc. En la misma función, también puede usarlo como un no puntero.
Inanc Gumus
130

Hay una función de biblioteca en el paquete de cadenas llamada Join: http://golang.org/pkg/strings/#Join

Una mirada al código de Joinmuestra un enfoque similar a la función Append que Kinopiko escribió: https://golang.org/src/strings/strings.go#L420

Uso:

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
fuente
21
No funciona cuando tienes que recorrer algo que no es una cadena [].
Malcolm
42

Acabo de comparar la respuesta superior publicada anteriormente en mi propio código (un recorrido recursivo en árbol) y el operador concat simple es en realidad más rápido que el 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()
}

Esto tardó 0,81 segundos, mientras que el siguiente código:

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
} 

solo tomó 0.61 segundos. Esto probablemente se deba a la sobrecarga de crear el nuevo BufferString.

Actualización: también comparé la joinfunción y funcionó en 0,54 segundos.

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
fuente
55
Creo que el OP estaba más preocupado por la complejidad de la memoria que por la complejidad del tiempo de ejecución, dado el hecho de que las ingeniosas concatenaciones de cadenas resultan en nuevas asignaciones de memoria cada vez.
galaktor
15
La velocidad lenta de esto bien podría estar relacionada con el uso de fmt.Fprint en lugar de buffer.WriteString("\t"); buffer.WriteString(subs[i]);
Robert Jack Will
Me alegra saber que mi método preferido de (strings.Join)ejecución como el más rápido mientras que desde esta diciendo que (bytes.Buffer)es el ganador!
Chetabahana
23

Podrías crear una gran porción de bytes y copiar los bytes de las cadenas cortas usando rebanadas de cadena. Hay una función dada en "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;
}

Luego, cuando finalicen las operaciones, utilícelas string ( )en la gran porción de bytes para convertirla nuevamente en una cadena.


fuente
Es interesante que haya tantas formas de hacer esto en Go.
Itzjak el
11
En la práctica, también dice que la idea es tan útil que fue capturada en una construcción. Así que puede reemplazar su función con append(slice, byte...), al parecer.
Aktau
23

Esta es la solución más rápida que no requiere que usted sepa o calcule primero el tamaño total del búfer:

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

Según mi punto de referencia , es un 20% más lento que la solución de copia (8.1ns por apéndice en lugar de 6.72ns) pero aún 55% más rápido que usar bytes.

rog
fuente
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
fuente
2
¡Bienvenido a Stack Overflow! Tómese un momento para leer la ayuda de edición en el centro de ayuda. El formateo en Stack Overflow es diferente al de otros sitios.
Rizier123
2
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. Por favor, trate de no saturar su código con comentarios explicativos, ¡esto reduce la legibilidad tanto del código como de las explicaciones!
Rizier123
Solución simple 👍
Finn
22

Nota agregada en 2018

De Go 1.10 hay un strings.Buildertipo, eche un vistazo a esta respuesta para obtener más detalles .

Respuesta previa a 201x

El código de referencia de @ cd1 y otras respuestas son incorrectas. b.Nno se supone que se establezca en la función de referencia. La herramienta go test la establece dinámicamente para determinar si el tiempo de ejecución de la prueba es estable.

Una función de referencia debe ejecutar los mismos b.Ntiempos de prueba y la prueba dentro del ciclo debe ser la misma para cada iteración. Así que lo arreglo agregando un bucle interno. También agrego puntos de referencia para algunas otras soluciones:

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

El entorno es OS X 10.11.6, 2.2 GHz Intel Core i7

Resultados de la prueba:

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

Conclusión:

  1. CopyPreAllocatees la forma más rápida AppendPreAllocateestá bastante cerca del número 1, pero es más fácil escribir el código.
  2. Concattiene un rendimiento realmente malo tanto en velocidad como en uso de memoria. No lo uses
  3. Buffer#Writey Buffer#WriteStringson básicamente iguales en velocidad, al contrario de lo que dijo @ Dani-Br en el comentario. Teniendo en cuenta que sí stringestá []byteen Go, tiene sentido.
  4. bytes.Buffer básicamente usa la misma solución que Copycon la contabilidad adicional y otras cosas.
  5. Copyy Appenduse un tamaño de bootstrap de 64, igual que los bytes.
  6. Appendusa más memoria y asignaciones, creo que está relacionado con el algoritmo de crecimiento que usa. No está creciendo la memoria tan rápido como los bytes.

Sugerencia:

  1. Para tareas simples como lo que OP quiere, usaría Appendo AppendPreAllocate. Es lo suficientemente rápido y fácil de usar.
  2. Si necesita leer y escribir el búfer al mismo tiempo, utilice, bytes.Bufferpor supuesto. Para eso está diseñado.
PickBoy
fuente
13

Mi sugerencia original fue

s12 := fmt.Sprint(s1,s2)

Pero la respuesta anterior usa bytes.Buffer - WriteString () es la forma más eficiente.

Mi sugerencia inicial usa reflexión y un interruptor de tipo. Ver (p *pp) doPrinty(p *pp) printArg
no hay una interfaz universal Stringer () para tipos básicos, como ingenuamente pensé.

Al menos, sin embargo, Sprint () usa internamente un bytes. Así

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

es aceptable en términos de asignaciones de memoria.

=> La concatenación Sprint () se puede utilizar para la salida de depuración rápida.
=> De lo contrario, use bytes.Buffer ... WriteString

Peter Buchmann
fuente
8
No está integrado y no es eficiente.
peterSO
Importar un paquete (como fmt) significa que no está integrado. Está en la biblioteca estándar.
Malcolm
Es lento solo porque usa la reflexión sobre sus argumentos. Es eficiente. De lo contrario, no es menos eficiente que unirse con cuerdas
Únete
11

Ampliando la respuesta de cd1: puede usar append () en lugar de copy (). append () hace provisiones anticipadas cada vez más grandes, cuesta un poco más de memoria, pero ahorra tiempo. Agregué dos puntos de referencia más en la parte superior de los suyos. Ejecutar localmente con

go test -bench=. -benchtime=100ms

En mi thinkpad T400s produce:

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

Esta es la versión real de benchmark proporcionada por @ cd1 ( Go 1.8, linux x86_64) con las correcciones de errores mencionados por @icza y @PickBoy.

Bytes.Bufferes solo 7veces más rápido que la concatenación directa de cadenas a través del +operador.

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

Tiempos:

BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op
Vitaly Isaev
fuente
No creo que configurar manualmente bN sea la forma correcta de usar las funciones de referencia del paquete de prueba
PickBoy
@PickBoy, justifica tu punto de vista. ¿Por qué crees que b.Nes una variable pública?
Vitaly Isaev
1
No se supone que bN esté configurado en la función de referencia. Está configurado por la herramienta Go Test dinámicamente. Una función de referencia debería ejecutar la misma prueba bN veces, pero en su código (así como en el código de @ cd1), cada prueba en el bucle es una prueba diferente (porque la longitud de la cadena está creciendo)
PickBoy
@PickBoy, si dejas que la herramienta de prueba vaya configurada b.Ndinámicamente, terminarás con cadenas de diferente longitud en diferentes casos de prueba. Ver comentario
Vitaly Isaev
Es por eso que debe agregar un bucle interno de un número fijo de iteraciones, como 10000, dentro del bucle bN.
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
fuente
1

Lo hago usando lo siguiente: -

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
fuente
Esto no resuelve el problema de OP de construir una cadena a través de una serie de iteraciones, con un bucle 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))
}
rajni kant
fuente
3
Por favor, no publique código solo respuestas. Dé una explicación de lo que hace este código y por qué es la solución.
Korashen
-1

resultado de referencia con estadísticas de asignación de memoria. verifique el código de referencia en github .

use strings.Builder para optimizar el rendimiento.

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
fuente
otorgue crédito a @ cd1 por los casos de prueba originales que está desarrollando aquí.
colm.anseo
-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
usuario2288856
fuente
55
Esta es una solución muy lenta, porque usa la reflexión, analiza la cadena de formato y hace una copia de los datos para la []byte(s1)conversión. Comparándolo con otras soluciones publicadas, ¿puede nombrar una ventaja única de su solución?
pts
-5

strings.Join() del paquete "cadenas"

Si tiene una falta de coincidencia de tipos (como si intenta unir un int y una cadena), hace RANDOMTYPE (lo que desea cambiar)

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

Salida:

hello all you people in here
liam
fuente
44
Este código ni siquiera se compila: strings.Join()solo toma 2 parámetros: un segmento y un separador string.
icza
esto no puede ayudar
Anshu
agregue algunos cambios aquí.
Anshu