W Go a string
jest prymitywnym typem, co oznacza, że jest tylko do odczytu, a każda jego manipulacja utworzy nowy ciąg.
Więc jeśli chcę wielokrotnie łączyć łańcuchy bez znajomości długości łańcucha wynikowego, jaki jest najlepszy sposób na to?
Naiwnym sposobem byłoby:
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
ale to nie wydaje się bardzo skuteczne.
string
go
string-concatenation
Randy Sugianto „Yuku”
źródło
źródło
append()
w języku, co jest dobrym rozwiązaniem. Będzie działał szybko jak,copy()
ale najpierw powiększy wycinek, nawet jeśli oznacza to przydzielenie nowej tablicy podkładowej, jeśli pojemność nie jest wystarczająca.bytes.Buffer
nadal ma sens, jeśli chcesz skorzystać z dodatkowych metod wygody lub jeśli oczekuje tego pakiet, którego używasz.1 + 2 + 3 + 4 + ...
. Jest ton*(n+1)/2
obszar trójkąta podstawyn
. Przydzielasz rozmiar 1, następnie rozmiar 2, następnie rozmiar 3 itd., Gdy dołączasz niezmienne ciągi w pętli. Kwadratyczne zużycie zasobów przejawia się na wiele sposobów.Odpowiedzi:
Nowy sposób:
Od wersji 1.10 istnieje
strings.Builder
typ, spójrz na tę odpowiedź, aby uzyskać więcej szczegółów .Stara droga:
Skorzystaj z
bytes
pakietu. MaBuffer
typ, który implementujeio.Writer
.Robi to w czasie O (n).
źródło
buffer := bytes.NewBufferString("")
możesz zrobićvar buffer bytes.Buffer
. Nie potrzebujesz również żadnego z tych średników :).Najbardziej efektywnym sposobem łączenia łańcuchów jest użycie wbudowanej funkcji
copy
. W moich testach takie podejście jest około 3 razy szybsze niż używaniebytes.Buffer
i znacznie dużo szybsze (około 12 000 razy) niż używanie operatora+
. Ponadto zużywa mniej pamięci.Stworzyłem przypadek testowy, aby to udowodnić, a oto wyniki:
Poniżej znajduje się kod do testowania:
źródło
buffer.Write
(bajty) jest o 30% szybszy niżbuffer.WriteString
. [przydatne, jeśli możesz uzyskać dane jako[]byte
]b.N
, więc nie porównujesz czasu wykonania tego samego zadania, które ma zostać wykonane (np. Jedna funkcja może dołączyć1,000
ciągi znaków, inna może dołączyć,10,000
co może mieć dużą różnicę w średniej na przykład czas 1 dołączeniaBenchmarkConcat()
). W każdym przypadku powinieneś użyć tej samej liczby dodatków (z pewnością nieb.N
) i wykonać całą konkatenację w cielefor
odb.N
( do 2for
osadzonych pętli).Przejdź 1.10+ istnieje
strings.Builder
, tutaj .Przykład
Jest prawie tak samo z
bytes.Buffer
.Kliknij, aby zobaczyć to na placu zabaw .
Uwaga
Obsługiwane interfejsy
Metody StringBuilder są wdrażane z myślą o istniejących interfejsach. Abyś mógł łatwo przejść do nowego typu Konstruktora w swoim kodzie.
Różnice w stosunku do bajtów Bufor
Może tylko rosnąć lub resetować.
Ma wbudowany mechanizm kopiowania, który zapobiega przypadkowemu skopiowaniu:
func (b *Builder) copyCheck() { ... }
W
bytes.Buffer
, można uzyskać dostęp do bajtów leżących jak to:(*Buffer).Bytes()
.strings.Builder
zapobiega temu problemowi.io.Reader
itp.Sprawdź jego kod źródłowy, aby uzyskać więcej informacji, tutaj .
źródło
strings.Builder
wdraża swoje metody za pomocą odbiornika wskaźnikowego, który rzucił mnie na chwilę. W rezultacie prawdopodobnie stworzyłbym taki przy użyciunew
.W pakiecie ciągów znajduje się funkcja biblioteki o nazwie
Join
: http://golang.org/pkg/strings/#JoinSpojrzenie na kod
Join
pokazuje podobne podejście do funkcji Append, którą Kinopiko napisał: https://golang.org/src/strings/strings.go#L420Stosowanie:
źródło
Właśnie porównałem najlepsze odpowiedzi zamieszczone powyżej w moim własnym kodzie (rekursywny spacer po drzewie), a prosty operator konkat jest w rzeczywistości szybszy niż
BufferString
.Zajęło to 0,81 sekundy, podczas gdy następujący kod:
zajęło tylko 0,61 sekundy. Jest to prawdopodobnie spowodowane nakładem pracy związanym z tworzeniem nowego
BufferString
.Aktualizacja: Testowałem również tę
join
funkcję i działała ona w 0,54 sekundy.źródło
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
run jak najszybciej, gdy z tego mówiąc, że(bytes.Buffer)
jest zwycięzcą!Możesz utworzyć duży kawałek bajtów i skopiować do niego bajty krótkich ciągów, używając plasterków ciągów. W „Effective Go” podano funkcję:
Następnie po zakończeniu operacji użyj
string ( )
dużego kawałka bajtów, aby ponownie przekonwertować go na ciąg znaków.źródło
append(slice, byte...)
Wygląda na to, że możesz zastąpić swoją funkcję .Jest to najszybsze rozwiązanie, które nie wymaga wcześniejszej znajomości ani obliczenia całkowitego rozmiaru bufora:
Według mojego testu jest o 20% wolniejszy niż rozwiązanie do kopiowania (8,1ns na dołączenie zamiast 6,72ns), ale nadal 55% szybszy niż użycie bajtów. Bufor.
źródło
źródło
Uwaga dodana w 2018 r
Od wersji 1.10 istnieje
strings.Builder
typ, spójrz na tę odpowiedź, aby uzyskać więcej szczegółów .Odpowiedź sprzed 201x
Kod testu porównawczego @ cd1 i inne odpowiedzi są nieprawidłowe.
b.N
nie powinien być ustawiony w funkcji testu porównawczego. Jest on ustawiany dynamicznie przez narzędzie testowe w celu ustalenia, czy czas wykonania testu jest stabilny.Funkcja testu porównawczego powinna uruchamiać te same
b.N
czasy testu, a test wewnątrz pętli powinien być taki sam dla każdej iteracji. Naprawiam to, dodając wewnętrzną pętlę. Dodam również testy porównawcze dla niektórych innych rozwiązań:Środowisko to OS X 10.11.6, 2,2 GHz Intel Core i7
Wyniki testów:
Wniosek:
CopyPreAllocate
jest najszybszym sposobem;AppendPreAllocate
jest dość blisko numeru 1, ale łatwiej jest napisać kod.Concat
ma naprawdę niską wydajność zarówno pod względem szybkości, jak i zużycia pamięci. Nie używaj tego.Buffer#Write
iBuffer#WriteString
są w zasadzie takie same pod względem prędkości, w przeciwieństwie do tego, co powiedział @ Dani-Br w komentarzu. Rozważaniestring
jest rzeczywiście[]byte
w Go, ma to sens.Copy
przypadku dodatkowej księgowości i innych rzeczy.Copy
iAppend
użyj rozmiaru bootstrapu 64, takiego samego jak bytes.BufferAppend
zużywają więcej pamięci i alokacji, myślę, że jest to związane z algorytmem wzrostu, którego używa. Nie rośnie pamięć tak szybko jak bajty. BuforSugestia:
Append
lubAppendPreAllocate
. Jest wystarczająco szybki i łatwy w użyciu.bytes.Buffer
oczywiście. Do tego jest przeznaczony.źródło
Moja oryginalna sugestia brzmiała
Ale powyżej odpowiedzi za pomocą bytes.Buffer - WriteString () jest najbardziej wydajnym sposobem.
Moja początkowa sugestia wykorzystuje odbicie i przełącznik typu. Zobacz
(p *pp) doPrint
i(p *pp) printArg
nie ma uniwersalnego interfejsu Stringer () dla podstawowych typów, jak naiwnie myślałem.
Przynajmniej Sprint () wewnętrznie używa bajtu. Bufor. A zatem
jest akceptowalny pod względem alokacji pamięci.
=> Łączenie Sprint () może być użyte do szybkiego wyjścia danych debugowania.
=> W przeciwnym razie użyj bytes.Buffer ... WriteString
źródło
Rozwijanie odpowiedzi cd1: Możesz użyć append () zamiast copy (). append () wprowadza coraz większe rezerwy, kosztując nieco więcej pamięci, ale oszczędzając czas. Dodałem dwa kolejne testy na górze twojego. Uruchom lokalnie z
Na moim thinkpadie T400s daje:
źródło
To jest rzeczywista wersja testu porównawczego dostarczonego przez @ cd1 (
Go 1.8
,linux x86_64
) z poprawkami błędów wspomnianymi przez @icza i @PickBoy.Bytes.Buffer
jest tylko kilka7
razy szybszy niż bezpośrednie łączenie łańcucha przez+
operatora.Czasy:
źródło
b.N
jest to zmienna publiczna?b.N
dynamicznie, skończysz z łańcuchami o różnej długości w różnych przypadkach testowych. Patrz komentarzgoutils.JoinBetween
źródło
Robię to za pomocą:
źródło
źródło
wynik testu porównawczego ze statystykami alokacji pamięci. sprawdź kod testu na github .
użyj strings.Builder do optymalizacji wydajności.
źródło
źródło
[]byte(s1)
konwersji. Porównując go z innymi opublikowanymi rozwiązaniami, czy możesz wymienić jedną zaletę swojego rozwiązania?strings.Join()
z pakietu „strings”Jeśli masz niedopasowanie typu (np. Jeśli próbujesz połączyć liczbę całkowitą i ciąg znaków), wykonujesz RANDOMTYPE (rzecz, którą chcesz zmienić)
DAWNY:
Wynik :
źródło
strings.Join()
pobiera tylko 2 parametry: plasterek i separatorstring
.