No Go, a string
é um tipo primitivo, o que significa que é somente leitura, e toda manipulação dele criará uma nova string.
Portanto, se eu quiser concatenar seqüências de caracteres várias vezes sem saber o tamanho da sequência resultante, qual é a melhor maneira de fazer isso?
A maneira ingênua seria:
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
mas isso não parece muito eficiente.
string
go
string-concatenation
Randy Sugianto 'Yuku'
fonte
fonte
append()
do idioma, o que é uma boa solução para isso. Ele terá um desempenho rápido,copy()
mas aumentará a fatia primeiro, mesmo que isso signifique alocar uma nova matriz de apoio, se a capacidade não for suficiente.bytes.Buffer
ainda faz sentido se você deseja métodos de conveniência adicionais ou se o pacote que você está usando espera.1 + 2 + 3 + 4 + ...
. Én*(n+1)/2
a área de um triângulo de basen
. Você aloca o tamanho 1, depois o tamanho 2, depois o tamanho 3 etc. quando anexa seqüências imutáveis em um loop. Esse consumo quadrático de recursos se manifesta de mais maneiras do que apenas isso.Respostas:
Nova maneira:
No Go 1.10, existe um
strings.Builder
tipo, consulte esta resposta para obter mais detalhes .À moda antiga:
Use o
bytes
pacote. Tem umBuffer
tipo que implementaio.Writer
.Isso é feito em O (n) tempo.
fonte
buffer := bytes.NewBufferString("")
, você pode fazervar buffer bytes.Buffer
. Você também não precisa de nenhum desses pontos e vírgulas :).A maneira mais eficiente de concatenar seqüências de caracteres é usando a função interna
copy
. Nos meus testes, essa abordagem é ~ 3x mais rápida que o usobytes.Buffer
e muito mais rápida (~ 12.000x) do que o operador+
. Além disso, ele usa menos memória.Eu criei um caso de teste para provar isso e aqui estão os resultados:
Abaixo está o código para teste:
fonte
buffer.Write
(bytes) é 30% mais rápido quebuffer.WriteString
. [útil se você pode obter os dados como[]byte
]b.N
e, portanto, você não está comparando o tempo de execução da mesma tarefa a ser executada (por exemplo, uma função pode acrescentar1,000
strings, outra pode acrescentar, o10,000
que pode fazer uma grande diferença na média tempo de 1 anexo,BenchmarkConcat()
por exemplo). Você deve usar a mesma contagem de anexos em cada caso (certamente nãob.N
) e fazer toda a concatenação dentro do corpo dofor
intervalo atéb.N
(ou seja, 2for
loops incorporados).Em Go 1.10+ existe
strings.Builder
, aqui .Exemplo
É quase o mesmo com
bytes.Buffer
.Clique para ver isso no playground .
Nota
Interfaces Suportadas
Os métodos do StringBuilder estão sendo implementados com as interfaces existentes em mente. Para que você possa alternar facilmente para o novo tipo de construtor em seu código.
Diferenças de bytes.Buffer
Só pode crescer ou redefinir.
Possui um mecanismo copyCheck que impede copiá-lo acidentalmente:
func (b *Builder) copyCheck() { ... }
Em
bytes.Buffer
, pode-se acessar os bytes subjacentes como este:(*Buffer).Bytes()
.strings.Builder
evita esse problema.io.Reader
etc.Confira seu código-fonte para mais detalhes, aqui .
fonte
strings.Builder
implementa seus métodos usando um receptor de ponteiro, o que me impressionou por um momento. Como resultado, eu provavelmente criaria um usandonew
.Há uma função de biblioteca no pacote strings chamada
Join
: http://golang.org/pkg/strings/#JoinUma olhada no código de
Join
mostra uma abordagem semelhante à função Anexar que Kinopiko escreveu: https://golang.org/src/strings/strings.go#L420Uso:
fonte
Acabei de comparar a resposta principal postada acima em meu próprio código (uma caminhada recursiva na árvore) e o operador concat simples é realmente mais rápido que o
BufferString
.Isso levou 0,81 segundos, enquanto o código a seguir:
levou apenas 0,61 segundos. Provavelmente, isso se deve à sobrecarga de criação do novo
BufferString
.Atualização: Também testei a
join
função e ela foi executada em 0,54 segundos.fonte
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
corrida é o mais rápido, enquanto esse ditado(bytes.Buffer)
é o vencedor!Você pode criar uma grande fatia de bytes e copiar os bytes das cadeias curtas usando fatias de cadeia. Existe uma função dada em "Effective Go":
Então, quando as operações forem concluídas, use
string ( )
a grande fatia de bytes para convertê-lo em uma sequência novamente.fonte
append(slice, byte...)
, ao que parece.Esta é a solução mais rápida que não requer que você saiba ou calcule primeiro o tamanho geral do buffer:
Pelo meu benchmark , é 20% mais lento que a solução de cópia (8,1ns por anexo em vez de 6,72ns), mas ainda 55% mais rápido que o uso de bytes.Buffer.
fonte
fonte
Nota adicionada em 2018
No Go 1.10, existe um
strings.Builder
tipo, consulte esta resposta para obter mais detalhes .Resposta pré-201x
O código de referência de @ cd1 e outras respostas estão incorretas.
b.N
não deve ser definido na função de benchmark. É definido pela ferramenta go test dinamicamente para determinar se o tempo de execução do teste é estável.Uma função de benchmark deve executar os mesmos
b.N
tempos de teste e o teste dentro do loop deve ser o mesmo para cada iteração. Então, eu o corrigo adicionando um loop interno. Também adiciono benchmarks para outras soluções:O ambiente é OS X 10.11.6, 2.2 GHz Intel Core i7
Resultado dos testes:
Conclusão:
CopyPreAllocate
é o caminho mais rápido;AppendPreAllocate
está bem próximo do número 1, mas é mais fácil escrever o código.Concat
tem um desempenho realmente ruim, tanto para velocidade quanto para uso de memória. Não use.Buffer#Write
eBuffer#WriteString
são basicamente os mesmos em velocidade, ao contrário do que @ Dani-Br disse no comentário. Considerando questring
está de fato[]byte
em Go, faz sentido.Copy
a manutenção extra de livros e outras coisas.Copy
eAppend
use um tamanho de inicialização de 64, o mesmo que bytes.Append
use mais memória e aloque, acho que está relacionado ao algoritmo de crescimento usado. Não está aumentando a memória tão rápido quanto bytes.Sugestão:
Append
ouAppendPreAllocate
. É rápido o suficiente e fácil de usar.bytes.Buffer
claro. É para isso que ele foi projetado.fonte
Minha sugestão original foi
Mas acima de resposta usando bytes.Buffer - WriteString () é a maneira mais eficiente.
Minha sugestão inicial usa reflexão e uma opção de tipo. Veja
(p *pp) doPrint
e(p *pp) printArg
Não há interface universal Stringer () para tipos básicos, como eu pensava ingenuamente.
Pelo menos, Sprint () usa internamente um bytes.Buffer. portanto
é aceitável em termos de alocações de memória.
=> A concatenação Sprint () pode ser usada para saída rápida de depuração.
=> Caso contrário, use bytes.Buffer ... WriteString
fonte
Expandindo a resposta do cd1: você pode usar append () em vez de copy (). append () faz provisões antecipadas ainda maiores, custando um pouco mais de memória, mas economizando tempo. Eu adicionei mais dois benchmarks no topo do seu. Executar localmente com
No meu thinkpad T400s, ele produz:
fonte
Esta é a versão real do benchmark fornecida por @ cd1 (
Go 1.8
,linux x86_64
) com as correções de bugs mencionados por @icza e @PickBoy.Bytes.Buffer
é apenas7
mais rápido que a concatenação direta de cadeias via+
operador.Horários:
fonte
b.N
é uma variável pública?b.N
dinamicamente, você terminará com uma sequência de diferentes comprimentos em diferentes casos de teste. Veja o comentáriogoutils.JoinBetween
fonte
Eu faço isso usando o seguinte: -
fonte
fonte
resultado de benchmark com estatísticas de alocação de memória. verifique o código de referência no github .
use strings.Builder para otimizar o desempenho.
fonte
fonte
[]byte(s1)
conversão. Comparando-o com outras soluções postadas, você pode citar uma única vantagem da sua solução?strings.Join()
do pacote "strings"Se você tem uma incompatibilidade de tipo (como se você estiver tentando ingressar em um int e uma string), você faz RANDOMTYPE (coisa que deseja alterar)
EX:
Resultado :
fonte
strings.Join()
usa apenas 2 parâmetros: uma fatia e um separadorstring
.