concatenar caracteres string go string-concatenation

string - caracteres - ¿Cómo concatenar eficientemente cadenas en Go?



concatenar caracteres en java (20)

Actualización 2018-04-03

A partir de la here 1.10, se recomienda que bytes.Buffer sustituya a bytes.Buffer . Compruebe las notas de la versión 1.10

Un nuevo tipo Builder es un reemplazo de bytes.Buffer para el caso de uso de acumulación de texto en un resultado de cadena. La API del Generador es un subconjunto restringido de bytes. Es posible que evite realizar copias duplicadas de los datos durante el método String.

================================================== ==========

El código de referencia de @ cd1 y otras respuestas son incorrectos. Se supone que bN no se establece en la función de referencia. Lo establece dinámicamente la herramienta de prueba de ir para determinar si el tiempo de ejecución de la prueba es estable.

Una función de referencia debe ejecutar la misma prueba bN veces y la prueba dentro del bucle 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. CopyPreAllocate es la forma más rápida; AppendPreAllocate está bastante cerca del número 1, pero es más fácil escribir el código.
  2. Concat tiene un rendimiento realmente malo tanto para la velocidad como para el uso de la memoria. No lo uses
  3. Buffer#Write y Buffer#WriteString son básicamente lo mismo en velocidad, al contrario de lo que @ Dani-Br dijo en el comentario. Teniendo en cuenta que la string es de hecho []byte en Go, tiene sentido.
  4. bytes.Buffer básicamente usa la misma solución que Copy con contabilidad adicional y otras cosas.
  5. Copy y Append utilizan un tamaño de bootstrap de 64, lo mismo que bytes.Buffer
  6. Append usar más memoria y asignaciones, creo que está relacionado con el algoritmo de crecimiento que utiliza. No está creciendo la memoria tan rápido como los bytes.

Sugerencia:

  1. Para tareas sencillas como lo que quiere OP, usaría Append o AppendPreAllocate . Es lo suficientemente rápido y fácil de usar.
  2. Si necesita leer y escribir el búfer al mismo tiempo, use bytes.Buffer por supuesto. Para eso está diseñado.

En Go, una string es 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.


A partir de Go 1.10 hay un strings.Builder , here .

Un generador se utiliza para construir de forma eficiente una cadena utilizando métodos de escritura. Minimiza la copia de memoria. El valor cero está listo para usar.

Uso:

Es casi lo mismo con los bytes.Buffer . bytes.Buffer .

package main import ( "strings" "fmt" ) func main() { var str strings.Builder for i := 0; i < 1000; i++ { str.WriteString("a") } fmt.Println(str.String()) }

Métodos de StringBuilder e interfaces que soporta:

Sus métodos se implementan teniendo en cuenta las interfaces existentes para que pueda cambiar al nuevo Generador fácilmente en su código.

Uso de valor cero:

var sb strings.Builder

Diferencias de bytes. Buffer:

  • Solo puede crecer o reiniciar.

  • En bytes. Los bytes subyacentes del bytes.Buffer pueden escapar de esta forma: (*Buffer).Bytes() ; strings.Builder previene este problema.

  • También tiene un mecanismo copyCheck en el interior que evita que se copie accidentalmente ( func (b *Builder) copyCheck() { ... } ).

Echa un vistazo a su código fuente here .


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

Sólo tomó 0,61 segundos. Esto se debe probablemente a la sobrecarga de crear el nuevo BufferString .

Actualización: También evalué la función de join y se ejecutó 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,"") }


Ampliando la respuesta de cd1: Puede usar append () en lugar de copy (). append () hace provisiones anticipadas cada vez más grandes, costando un poco más de memoria, pero ahorrando tiempo. Añadí dos puntos de referencia más en la parte superior de la suya. 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


Eche un vistazo a la biblioteca strconv de strconv que da acceso a varias funciones AppendXX, lo que nos permite concatenar cadenas con cadenas y otros tipos de datos.


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

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

Según mi benchmark , 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 así es un 55% más rápido que usar bytes.


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.Buffer es solo 7 veces más rápido que la concatenación de cadena directa 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


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 Unión muestra un enfoque similar a la función de anexión 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


La forma más eficiente de concatenar cadenas es mediante la función integrada copy . En mis pruebas, ese enfoque es ~ 3x más rápido que usar Buffer y mucho más rápido (~ 12,000x) que usar el operador + . Además, utiliza menos memoria.

He creado un caso de prueba para demostrar 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) } }


La mejor manera es usar el paquete de bytes . Tiene un tipo de Buffer 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 tiempo O (n).

Nota añadida en 2018

Desde Go 1.10 está el tipo strings.Builder , que logra esto aún más eficientemente (para cadenas). El ejemplo dado allí es sucinto y fácil de copiar / adaptar.

Esto es análogo a la clase StringBuilder en Java.


Lo hago utilizando 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 }


Mi sugerencia original fue

s12 := fmt.Sprint(s1,s2)

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

Mi sugerencia inicial utiliza la reflexión y un tipo de cambio. Consulte (p *pp) doPrint y (p *pp) printArg
No hay una interfaz universal de Stringer () para los tipos básicos, como había pensado ingenuamente.

Sin embargo, al menos, Sprint () utiliza internamente un byte. Buffer. Así

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

Es aceptable en términos de asignaciones de memoria.

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



Podría crear una gran porción de bytes y copiar los bytes de las cadenas cortas en ella usando porciones 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 las operaciones hayan finalizado, use string ( ) en la gran porción de bytes para convertirla nuevamente en una cadena.


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

utilizar cadenas.creador 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


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


strings.Join() del paquete "strings"

Si tiene una falta de coincidencia de tipo (como si está intentando unir un int y una cadena), realice RANDOMTYPE (cosa que desea cambiar)

EX:

package main import "strings" var intEX = 0 var stringEX = "hello all you " var stringEX2 = " people in here" func main() { strings.Join(stringEX, string(intEX), stringEX2) }


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


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


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