Menu

StringBuilder di C#: Append, Insert, Replace dan Kapan Memakainya

Kenapa penggabungan string berulang di dalam perulangan itu lambat di C#, dan bagaimana StringBuilder mengatasinya: Append, AppendLine, AppendFormat, Insert, Remove, Replace, capacity, serta kapan penggabungan biasa atau string.Join adalah pilihan yang lebih baik.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

String di C# bersifat immutable: s += "x" tidak memperpanjang s, tetapi membuat string baru yang berisi karakter lama plus karakter baru, dan string lama menjadi sampah. Jika hanya sekali, itu bukan masalah. Di perulangan yang berjalan ribuan kali, setiap iterasi menyalin semua yang sudah dibangun sejauh ini, sehingga total pekerjaannya tumbuh sebanding dengan kuadrat panjangnya.

StringBuilder, di System.Text, adalah buffer yang bisa diubah. Append menulis ke ruang kosong di ujungnya, sesekali memperbesar buffer, dan ToString() membuat satu string di akhir.

Output:

Order #1042, total 59.97
Status: shipped
**********
51

Kenapa penggabungan di dalam perulangan itu lambat

Berikut teks yang sama yang dibangun dengan kedua cara. Waktunya berbeda-beda di setiap komputer, jadi angka di bawah hanya contoh; yang penting adalah selisihnya, dan selisih itu makin lebar seiring perulangan makin panjang:

Contoh output:

string +=     482 ms, length 188890
StringBuilder 1 ms, length 188890

Dengan 20.000 kali append, versi += menyalin hampir 2 miliar karakter secara total. StringBuilder menyalin setiap karakter sekitar dua kali, sekali saat ditambahkan dan sekali lagi di ToString(), itulah sebabnya waktunya tetap sekitar satu milidetik.

Append, AppendLine, dan AppendFormat

Append punya overload untuk setiap tipe bawaan, dan mengembalikan builder yang sama, jadi pemanggilannya bisa dirangkai. AppendLine menambahkan baris baru setelah argumennya. AppendFormat menerima composite format string seperti string.Format:

Output:

RECEIPT
-------
Espresso        2.40
Croissant       3.10
Orange juice    4.50
TOTAL          10.00

Menambahkan interpolated string (sb.Append($"...")) tidak masalah dan enak dibaca. Di .NET 6 dan sesudahnya, compiler bahkan menulis potongan-potongannya langsung ke builder tanpa membuat string perantara.

AppendLine memakai Environment.NewLine: \n di Linux dan macOS, \r\n di Windows. Jika format file mewajibkan akhir baris tertentu, tambahkan sendiri.

Insert, Remove, Replace, dan indexer

Berbeda dengan string, StringBuilder bisa diedit langsung:

Output:

Hello, World
Hello, Maya
Maya
maya
maya!
[] length 0

Mengisi Length dengan nilai yang lebih kecil akan memotong isinya; ini cara termurah untuk membuang pemisah di akhir. Clear() mengatur panjangnya menjadi 0 dan mempertahankan buffer yang sudah dialokasikan, jadi satu builder bisa dipakai ulang di setiap iterasi perulangan luar.

Indexer cepat untuk bagian yang baru ditambahkan, tetapi bisa lambat untuk akses acak ke builder yang sangat besar, karena isinya disimpan dalam potongan-potongan yang saling terhubung. Jika kamu perlu membaca karakter, panggil ToString() sekali lalu akses string-nya lewat indeks.

Masalah pemisah di akhir

Pemakaian klasiknya adalah membangun daftar yang dipisahkan koma. Menambahkan pemisah setelah setiap item menyisakan satu pemisah berlebih di akhir:

Output:

csharp, dotnet, linq
csharp, dotnet, linq

Jika item-itemnya sudah ada di dalam collection, string.Join lebih singkat dan setidaknya sama cepatnya. Simpan StringBuilder untuk teks yang bentuknya bergantung pada logika: bagian kondisional, perulangan bersarang, format campuran.

Capacity

StringBuilder dimulai dengan ruang untuk 16 karakter. Saat ruangnya habis, ia menyambungkan blok penyimpanan baru sebesar semua isi yang sudah disimpannya, sampai 8.000 karakter per blok, jadi capacity-nya berlipat dua selama masih kecil, lalu bertambah dengan langkah 8.000 karakter. Pertumbuhan itu tidak pernah menyalin teks yang sudah tersimpan. Jika kamu kira-kira tahu panjang hasilnya, berikan capacity ke constructor, dan builder itu tidak perlu tumbuh sama sekali:

Output:

12000
10000
True
True

Menentukan ukuran di awal adalah optimasi, bukan keharusan. Pertumbuhan mengalokasikan blok baru tetapi tidak pernah memindahkan teks yang sudah ada, jadi nilai default sudah cukup, kecuali kamu membangun banyak string besar di bagian kode yang sering dijalankan.

Kapan tidak memakai StringBuilder

StringBuilder bukan pengganti yang lebih cepat untuk setiap +. Compiler sudah mengubah a + b + c + d dalam satu ekspresi menjadi satu pemanggilan string.Concat, yang menghitung panjang akhirnya dan menyalin setiap potongan sekali, pekerjaan yang sama dengan yang akan dilakukan builder, hanya dengan kode yang lebih banyak. Pakai string biasa jika:

  • Jumlah potongannya sedikit dan tetap: $"{first} {last} ({age})".
  • Potongannya ada di dalam collection dan butuh pemisah: string.Join(", ", items).
  • Potongannya tidak butuh pemisah: string.Concat(parts).
  • Kamu menggabungkan beberapa kali saja di luar perulangan.

Pakai StringBuilder saat kamu menambahkan teks di dalam perulangan, menambahkan teks dalam jumlah yang tidak kamu ketahui sebelumnya, atau mengedit teksnya (menyisipkan, mengganti, menghapus) sambil membangunnya. Bolak-balik mengonversi (sb.ToString() di dalam perulangan) membuang manfaatnya; panggil sekali saja di akhir.

Stream adalah alternatif lainnya: untuk menghasilkan file besar, tulis baris-barisnya ke StreamWriter sambil jalan alih-alih membangun seluruh isinya di memori. Lihat file.

Pertanyaan yang sering diajukan

Apa itu StringBuilder di C#?

System.Text.StringBuilder adalah buffer karakter yang bisa diubah untuk membangun string dalam banyak langkah. Append menambahkan ke ujung buffer alih-alih membuat string baru setiap kali, dan ToString() menghasilkan string akhirnya sekali saja. Inilah alat standar untuk membangun teks di dalam perulangan.

Kapan sebaiknya memakai StringBuilder alih-alih penggabungan string?

Saat kamu menambahkan teks di dalam perulangan atau dalam jumlah yang tidak diketahui, misalnya saat membangun laporan, file CSV, atau HTML dari sebuah collection. Untuk beberapa potongan yang jumlahnya tetap (a + b + c, atau satu interpolated string), penggabungan biasa sama cepatnya dan lebih mudah dibaca, karena compiler sudah menggabungkannya menjadi satu pemanggilan string.Concat.

Bagaimana cara menghapus karakter terakhir dari StringBuilder?

Kurangi Length-nya: if (sb.Length > 0) sb.Length--; membuang karakter terakhir tanpa penyalinan. sb.Remove(sb.Length - 1, 1) melakukan hal yang sama. Ini cara biasa untuk membuang koma di akhir, meskipun string.Join sejak awal tidak menghasilkan pemisah di akhir.

Apakah StringBuilder thread-safe?

Tidak. Memanggil Append pada StringBuilder yang sama dari beberapa thread sekaligus bisa merusak isinya atau melempar exception. Beri setiap thread builder-nya sendiri lalu gabungkan hasilnya, atau lindungi builder yang dipakai bersama dengan lock.

Apa yang ditambahkan AppendLine di C#?

Teksnya, diikuti Environment.NewLine, yaitu \r\n di Windows dan \n di Linux dan macOS. Jika output-nya harus memakai akhir baris tertentu, misalnya \n untuk format file atau \r\n untuk header HTTP, tambahkan secara eksplisit dengan Append("\n").

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai