En Go, a 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.
string
go
string-concatenation
Randy Sugianto 'Yuku'
fuente
fuente
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.Buffer
aún tiene sentido si desea sus métodos de conveniencia adicionales o si el paquete que está utilizando lo espera.1 + 2 + 3 + 4 + ...
. Esn*(n+1)/2
, el área de un triángulo de basen
. 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.Respuestas:
Nueva manera:
De Go 1.10 hay un
strings.Builder
tipo, eche un vistazo a esta respuesta para obtener más detalles .Vieja forma:
Usa el
bytes
paquete. Tiene unBuffer
tipo que implementaio.Writer
.Esto lo hace en O (n) tiempo.
fuente
buffer := bytes.NewBufferString("")
, puedes hacerlovar buffer bytes.Buffer
. Tampoco necesitas ninguno de esos puntos y comas :).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 usarlobytes.Buffer
y 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:
A continuación se muestra el código para la prueba:
fuente
buffer.Write
(bytes) es un 30% más rápido quebuffer.WriteString
. [útil si puede obtener los datos como[]byte
]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 agregar1,000
cadenas, otra puede agregar, lo10,000
que 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 nob.N
), y hacer toda la concatenación dentro del cuerpo delfor
rango ab.N
(es decir, 2for
bucles incrustados).En Go 1.10+ hay
strings.Builder
, aquí .Ejemplo
Es casi lo mismo con
bytes.Buffer
.Haga clic para ver esto en el patio de recreo .
Nota
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.io.Reader
etc.Mira su código fuente para más detalles, aquí .
fuente
strings.Builder
implementa sus métodos utilizando un receptor de puntero, que me arrojó por un momento. Como resultado, probablemente crearía uno usandonew
.Hay una función de biblioteca en el paquete de cadenas llamada
Join
: http://golang.org/pkg/strings/#JoinUna mirada al código de
Join
muestra un enfoque similar a la función Append que Kinopiko escribió: https://golang.org/src/strings/strings.go#L420Uso:
fuente
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
.Esto tardó 0,81 segundos, mientras que el siguiente código:
solo tomó 0.61 segundos. Esto probablemente se deba a la sobrecarga de crear el nuevo
BufferString
.Actualización: también comparé la
join
función y funcionó en 0,54 segundos.fuente
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
ejecución como el más rápido mientras que desde esta diciendo que(bytes.Buffer)
es el ganador!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":
Luego, cuando finalicen las operaciones, utilícelas
string ( )
en la gran porción de bytes para convertirla nuevamente en una cadena.fuente
append(slice, byte...)
, al parecer.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:
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.
fuente
fuente
Nota agregada en 2018
De Go 1.10 hay un
strings.Builder
tipo, 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.N
no 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.N
tiempos 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:El entorno es OS X 10.11.6, 2.2 GHz Intel Core i7
Resultados de la prueba:
Conclusión:
CopyPreAllocate
es la forma más rápidaAppendPreAllocate
está bastante cerca del número 1, pero es más fácil escribir el código.Concat
tiene un rendimiento realmente malo tanto en velocidad como en uso de memoria. No lo usesBuffer#Write
yBuffer#WriteString
son básicamente iguales en velocidad, al contrario de lo que dijo @ Dani-Br en el comentario. Teniendo en cuenta que sístring
está[]byte
en Go, tiene sentido.Copy
con la contabilidad adicional y otras cosas.Copy
yAppend
use un tamaño de bootstrap de 64, igual que los bytes.Append
usa 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:
Append
oAppendPreAllocate
. Es lo suficientemente rápido y fácil de usar.bytes.Buffer
por supuesto. Para eso está diseñado.fuente
Mi sugerencia original fue
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) doPrint
y(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í
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
fuente
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
En mi thinkpad T400s produce:
fuente
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 solo7
veces más rápido que la concatenación directa de cadenas a través del+
operador.Tiempos:
fuente
b.N
es una variable pública?b.N
dinámicamente, terminarás con cadenas de diferente longitud en diferentes casos de prueba. Ver comentariogoutils.JoinBetween
fuente
Lo hago usando lo siguiente: -
fuente
fuente
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.
fuente
fuente
[]byte(s1)
conversión. Comparándolo con otras soluciones publicadas, ¿puede nombrar una ventaja única de su solución?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:
Salida:
fuente
strings.Join()
solo toma 2 parámetros: un segmento y un separadorstring
.