Di Go, a string
adalah tipe primitif, yang berarti hanya baca, dan setiap manipulasi akan membuat string baru.
Jadi jika saya ingin menyatukan string berkali-kali tanpa mengetahui panjang dari string yang dihasilkan, apa cara terbaik untuk melakukannya?
Cara naifnya adalah:
s := ""
for i := 0; i < 1000; i++ {
s += getShortStringFromSomewhere()
}
return s
tetapi itu tampaknya tidak terlalu efisien.
string
go
string-concatenation
Randy Sugianto 'Yuku'
sumber
sumber
append()
masuk ke dalam bahasa, yang merupakan solusi yang bagus untuk ini. Ini akan melakukan seperti cepatcopy()
tetapi akan menumbuhkan irisan pertama bahkan jika itu berarti mengalokasikan array dukungan baru jika kapasitasnya tidak cukup.bytes.Buffer
masih masuk akal jika Anda menginginkan metode kenyamanan tambahan atau jika paket yang Anda gunakan mengharapkannya.1 + 2 + 3 + 4 + ...
. Itun*(n+1)/2
, area segitiga alasn
. Anda mengalokasikan ukuran 1, lalu ukuran 2, lalu ukuran 3, dll ketika Anda menambahkan string yang tidak dapat diubah dalam satu lingkaran. Konsumsi sumber daya kuadratik ini memanifestasikan dirinya dalam lebih dari sekadar ini.Jawaban:
Jalan baru:
Dari Go 1.10 ada
strings.Builder
tipe, silakan lihat jawaban ini untuk lebih detail .Cara lama:
Gunakan
bytes
paket. Ini memilikiBuffer
tipe yang mengimplementasikanio.Writer
.Ini melakukannya dalam waktu O (n).
sumber
buffer := bytes.NewBufferString("")
, Anda bisa melakukannyavar buffer bytes.Buffer
. Anda juga tidak memerlukan titik koma itu :).Cara paling efisien untuk menggabungkan string adalah menggunakan fungsi builtin
copy
. Dalam pengujian saya, pendekatan itu ~ 3x lebih cepat daripada menggunakanbytes.Buffer
dan jauh lebih cepat (~ 12.000x) daripada menggunakan operator+
. Selain itu, ia menggunakan lebih sedikit memori.Saya telah membuat test case untuk membuktikan ini dan berikut hasilnya:
Di bawah ini adalah kode untuk pengujian:
sumber
buffer.Write
(byte) lebih cepat 30% daribuffer.WriteString
. [berguna jika Anda bisa mendapatkan data sebagai[]byte
]b.N
, dan jadi Anda tidak membandingkan waktu eksekusi dari tugas yang sama untuk dijalankan (mis. Satu fungsi mungkin menambahkan1,000
string, yang lain mungkin menambahkan10,000
yang dapat membuat perbedaan besar dalam rata-rata waktu 1 tambahkan,BenchmarkConcat()
misalnya). Anda harus menggunakan jumlah append yang sama dalam setiap kasus (tentu saja tidakb.N
), dan melakukan semua penggabungan di dalam tubuhfor
mulaib.N
(yaitu, 2for
loop tertanam).Di Go 1.10+ ada
strings.Builder
, di sini .Contoh
Hampir sama dengan
bytes.Buffer
.Klik untuk melihat ini di taman bermain .
Catatan
Antarmuka yang didukung
Metode StringBuilder diimplementasikan dengan mempertimbangkan antarmuka yang ada. Sehingga Anda dapat beralih ke tipe Builder baru dengan mudah di kode Anda.
Perbedaan dari bytes.Buffer
Itu hanya bisa tumbuh atau diatur ulang.
Ini memiliki mekanisme copyCheck bawaan yang mencegah penyalinannya secara tidak sengaja:
func (b *Builder) copyCheck() { ... }
Dalam
bytes.Buffer
, satu dapat mengakses byte yang mendasari seperti ini:(*Buffer).Bytes()
.strings.Builder
mencegah masalah ini.io.Reader
dll.Lihat kode sumbernya untuk lebih jelasnya, di sini .
sumber
strings.Builder
mengimplementasikan metodenya menggunakan penerima pointer, yang membuat saya sejenak. Akibatnya, saya mungkin akan membuatnya menggunakannew
.Ada fungsi perpustakaan dalam paket string yang disebut
Join
: http://golang.org/pkg/strings/#JoinMelihat kode
Join
menunjukkan pendekatan yang mirip dengan fungsi Tambah Kinopiko menulis: https://golang.org/src/strings/strings.go#L420Pemakaian:
sumber
Saya baru saja membandingkan jawaban teratas yang diposting di atas dalam kode saya sendiri (pohon rekursif berjalan) dan operator concat sederhana sebenarnya lebih cepat daripada
BufferString
.Ini memakan waktu 0,81 detik, sedangkan kode berikut:
hanya butuh 0,61 detik. Ini mungkin karena overhead menciptakan yang baru
BufferString
.Pembaruan: Saya juga membandingkan
join
fungsi dan menjalankannya dalam 0,54 detik.sumber
buffer.WriteString("\t");
buffer.WriteString(subs[i]);
(strings.Join)
lari pilihan saya sebagai yang tercepat sementara dari pepatah ini(bytes.Buffer)
adalah pemenangnya!Anda bisa membuat irisan besar byte dan menyalin byte string pendek ke dalamnya menggunakan irisan string. Ada fungsi yang diberikan dalam "Efektif Go":
Kemudian ketika operasi selesai, gunakan
string ( )
potongan besar byte untuk mengubahnya menjadi string lagi.sumber
append(slice, byte...)
, sepertinya.Ini adalah solusi tercepat yang tidak mengharuskan Anda untuk mengetahui atau menghitung ukuran buffer keseluruhan terlebih dahulu:
Menurut tolok ukur saya , ini 20% lebih lambat dari solusi salin (8.1ns per append daripada 6.72ns) tetapi masih 55% lebih cepat daripada menggunakan bytes.Buffer.
sumber
sumber
Catatan ditambahkan pada 2018
Dari Go 1.10 ada
strings.Builder
tipe, silakan lihat jawaban ini untuk lebih detail .Pra-201x menjawab
Kode tolok ukur @ cd1 dan jawaban lain salah.
b.N
tidak seharusnya diatur dalam fungsi benchmark. Ini ditetapkan oleh alat uji go secara dinamis untuk menentukan apakah waktu pelaksanaan tes stabil.Fungsi benchmark harus menjalankan waktu tes yang sama
b.N
dan tes di dalam loop harus sama untuk setiap iterasi. Jadi saya memperbaikinya dengan menambahkan loop dalam. Saya juga menambahkan tolok ukur untuk beberapa solusi lain:Lingkungan adalah OS X 10.11.6, 2.2 GHz Intel Core i7
Hasil tes:
Kesimpulan:
CopyPreAllocate
adalah cara tercepat;AppendPreAllocate
cukup dekat dengan No.1, tetapi lebih mudah untuk menulis kode.Concat
memiliki kinerja yang sangat buruk baik untuk kecepatan dan penggunaan memori. Jangan gunakan itu.Buffer#Write
danBuffer#WriteString
pada dasarnya sama dalam kecepatan, bertentangan dengan apa yang dikatakan @ Dani-Br dalam komentar. Mengingatstring
memang[]byte
dalam Go, itu masuk akal.Copy
dengan pembukuan tambahan dan hal-hal lainnya.Copy
danAppend
gunakan ukuran bootstrap 64, sama seperti bytes.BufferAppend
menggunakan lebih banyak memori dan allocs, saya pikir itu terkait dengan algoritma tumbuh yang digunakannya. Ini tidak menumbuhkan memori secepat byte. BufferSaran:
Append
atauAppendPreAllocate
. Cukup cepat dan mudah digunakan.bytes.Buffer
saja. Untuk itulah dirancang.sumber
Saran asli saya adalah
Tetapi jawaban di atas menggunakan bytes.Buffer - WriteString () adalah cara yang paling efisien.
Saran awal saya menggunakan refleksi dan saklar jenis. Lihat
(p *pp) doPrint
dan(p *pp) printArg
Tidak ada antarmuka Stringer () universal untuk tipe dasar, seperti yang saya pikir naif.
Paling tidak, Sprint () secara internal menggunakan bytes.Buffer. Jadi
dapat diterima dalam hal alokasi memori.
=> Sprint () concatenation dapat digunakan untuk hasil debug cepat.
=> Kalau tidak gunakan bytes.Buffer ... WriteString
sumber
Memperluas jawaban cd1: Anda mungkin menggunakan append () alih-alih copy (). append () membuat ketentuan sebelumnya semakin besar, menghabiskan sedikit lebih banyak memori, tetapi menghemat waktu. Saya menambahkan dua tolok ukur di bagian atas Anda. Jalankan secara lokal dengan
Di T400s thinkpad saya menghasilkan:
sumber
Ini adalah versi aktual dari benchmark yang disediakan oleh @ cd1 (
Go 1.8
,linux x86_64
) dengan perbaikan bug yang disebutkan oleh @icza dan @PickBoy.Bytes.Buffer
hanya7
kali lebih cepat daripada penggabungan string langsung melalui+
operator.Pengaturan waktu:
sumber
b.N
variabel publik?b.N
secara dinamis, Anda akan berakhir dengan string dengan panjang yang berbeda di berbagai kasus uji. Lihat komentargoutils. Bergabunglah
sumber
Saya melakukannya menggunakan yang berikut: -
sumber
sumber
hasil benchmark dengan statistik alokasi memori. periksa kode tolok ukur di github .
gunakan strings.Builder untuk mengoptimalkan kinerja.
sumber
sumber
[]byte(s1)
konversi. Membandingkannya dengan solusi lain yang diposting, dapatkah Anda menyebutkan satu keuntungan dari solusi Anda?strings.Join()
dari paket "string"Jika Anda memiliki tipe ketidakcocokan (seperti jika Anda mencoba untuk bergabung dengan int dan string), Anda melakukan RANDOMTYPE (hal yang ingin Anda ubah)
EX:
Keluaran:
sumber
strings.Join()
hanya membutuhkan 2 parameter: slice dan separatorstring
.