Dans Go, a string
est 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.
string
go
string-concatenation
Randy Sugianto «Yuku»
la source
la source
append()
entrées dans la langue, ce qui est une bonne solution pour cela. Il fonctionnera rapidement commecopy()
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.Buffer
est toujours logique si vous voulez ses méthodes supplémentaires ou si le package que vous utilisez l'attend.1 + 2 + 3 + 4 + ...
. C'estn*(n+1)/2
l'aire d'un triangle de basen
. 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.Réponses:
Nouvelle façon:
À partir de Go 1.10, il existe un
strings.Builder
type, veuillez jeter un œil à cette réponse pour plus de détails .Old Way:
Utilisez le
bytes
package. Il a unBuffer
type qui implémenteio.Writer
.Cela le fait en temps O (n).
la source
buffer := bytes.NewBufferString("")
, vous pouvez le fairevar buffer bytes.Buffer
. Vous n'avez également besoin d'aucun de ces points-virgules :).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'utilisationbytes.Buffer
et 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:
Voici le code pour les tests:
la source
buffer.Write
(octets) est 30% plus rapide quebuffer.WriteString
. [utile si vous pouvez obtenir les données[]byte
]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 des1,000
chaînes, une autre peut ajouter,10,000
ce 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 pasb.N
) et faire toute la concaténation à l'intérieur du corps de lafor
plageb.N
(c'est-à-dire, 2for
boucles intégrées).Dans Go 1.10+, il y
strings.Builder
en a ici .Exemple
C'est presque la même chose avec
bytes.Buffer
.Cliquez pour voir ceci sur le terrain de jeu .
Remarque
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.io.Reader
etc.Consultez son code source pour plus de détails, ici .
la source
strings.Builder
met 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 utilisantnew
.Il y a une fonction de bibliothèque dans le package de chaînes appelée
Join
: http://golang.org/pkg/strings/#JoinUn regard sur le code de
Join
montre une approche similaire à la fonction Append Kinopiko a écrit: https://golang.org/src/strings/strings.go#L420Usage:
la source
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
.Cela a pris 0,81 seconde, alors que le code suivant:
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
join
fonction et elle a fonctionné en 0,54 seconde.la source
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
course préférée est la plus rapide alors que, d'après ce dicton, c'est(bytes.Buffer)
le gagnant!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":
Ensuite, lorsque les opérations sont terminées, utilisez
string ( )
sur la grande tranche d'octets pour la convertir à nouveau en chaîne.la source
append(slice, byte...)
, semble-t-il.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:
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.
la source
la source
Note ajoutée en 2018
À partir de Go 1.10, il existe un
strings.Builder
type, 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.N
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 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.N
temps 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:L'environnement est OS X 10.11.6, 2,2 GHz Intel Core i7
Résultats de test:
Conclusion:
CopyPreAllocate
est le moyen le plus rapide;AppendPreAllocate
est assez proche de No.1, mais il est plus facile d'écrire le code.Concat
a de très mauvaises performances à la fois pour la vitesse et l'utilisation de la mémoire. Ne l'utilisez pas.Buffer#Write
etBuffer#WriteString
sont fondamentalement les mêmes en vitesse, contrairement à ce que @ Dani-Br a dit dans le commentaire. Considérant questring
c'est bien[]byte
dans Go, ça a du sens.Copy
qu'avec la tenue de livres supplémentaires et d'autres choses.Copy
etAppend
utilisez une taille de bootstrap de 64, la même que celle des octets.Append
utiliser 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:
Append
ouAppendPreAllocate
. C'est assez rapide et facile à utiliser.bytes.Buffer
bien sûr. C'est pour ça qu'il est conçu.la source
Ma suggestion initiale était
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) doPrint
et(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
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
la source
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
Sur mon thinkpad T400s ça donne:
la source
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.Buffer
est seulement7
plus rapide que la concaténation directe de chaînes via l'+
opérateur.Calendrier:
la source
b.N
c'est une variable publique?b.N
dynamiquement, vous vous retrouverez avec des chaînes de longueur différente dans différents cas de test. Voir le commentairegoutils.
la source
Je le fais en utilisant ce qui suit: -
la source
la source
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.
la source
la source
[]byte(s1)
conversion. En le comparant à d'autres solutions publiées, pouvez-vous citer un seul avantage de votre solution?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:
Production :
la source
strings.Join()
ne prend que 2 paramètres: une tranche et un séparateurstring
.