Saya perlu mengganti banyak sub-string yang berbeda dalam sebuah string dengan cara yang paling efisien. apakah ada cara lain selain cara brute force untuk mengganti setiap bidang menggunakan string.replace?
97
Jika string yang Anda operasikan sangat panjang, atau Anda beroperasi pada banyak string, maka sebaiknya gunakan java.util.regex.Matcher (ini membutuhkan waktu di muka untuk mengompilasi, jadi tidak akan efisien. jika masukan Anda sangat kecil atau pola pencarian Anda sering berubah).
Di bawah ini adalah contoh lengkap, berdasarkan daftar token yang diambil dari peta. (Menggunakan StringUtils dari Apache Commons Lang).
Map<String,String> tokens = new HashMap<String,String>();
tokens.put("cat", "Garfield");
tokens.put("beverage", "coffee");
String template = "%cat% really needs some %beverage%.";
// Create pattern of the format "%(cat|beverage)%"
String patternString = "%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(template);
StringBuffer sb = new StringBuffer();
while(matcher.find()) {
matcher.appendReplacement(sb, tokens.get(matcher.group(1)));
}
matcher.appendTail(sb);
System.out.println(sb.toString());
Setelah ekspresi reguler dikompilasi, pemindaian string input biasanya sangat cepat (meskipun jika ekspresi reguler Anda rumit atau melibatkan mundur, Anda masih perlu melakukan tolok ukur untuk mengonfirmasi ini!)
"%(" + StringUtils.join(tokens.keySet(), "|") + ")%";
Algoritma
Salah satu cara paling efisien untuk mengganti string yang cocok (tanpa ekspresi reguler) adalah menggunakan algoritme Aho-Corasick dengan kinerja Trie (dibaca "coba"), algoritme hashing cepat , dan implementasi koleksi yang efisien .
Kode Sederhana
Solusi sederhana memanfaatkan Apache
StringUtils.replaceEach
sebagai berikut:Ini memperlambat teks besar.
Kode Cepat
Implementasi algoritma Aho-Corasick Bor memperkenalkan sedikit lebih banyak kompleksitas yang menjadi detail implementasi dengan menggunakan façade dengan tanda tangan metode yang sama:
Tolak ukur
Untuk tolok ukur, buffer dibuat menggunakan randomNumeric sebagai berikut:
Di mana
MATCHES_DIVISOR
menentukan jumlah variabel yang akan dimasukkan:Kode patokan itu sendiri ( JMH sepertinya berlebihan):
1.000.000: 1.000
Tolok ukur mikro sederhana dengan 1.000.000 karakter dan 1.000 string yang ditempatkan secara acak untuk diganti.
Tidak ada kontes.
10.000: 1.000
Menggunakan 10.000 karakter dan 1.000 string yang cocok untuk mengganti:
Kesenjangan ditutup.
1.000: 10
Menggunakan 1.000 karakter dan 10 string yang cocok untuk mengganti:
Untuk string pendek, overhead pengaturan Aho-Corasick melampaui pendekatan brute-force
StringUtils.replaceEach
.Pendekatan campuran berdasarkan panjang teks dimungkinkan, untuk mendapatkan yang terbaik dari kedua implementasi.
Implementasi
Pertimbangkan untuk membandingkan implementasi lain untuk teks dengan panjang lebih dari 1 MB, termasuk:
Dokumen
Makalah dan informasi yang berkaitan dengan algoritma:
sumber
Ini berhasil untuk saya:
Contoh:
Keluaran: apple-banana-frui-
sumber
Jika Anda akan mengubah String berkali-kali, biasanya lebih efisien menggunakan StringBuilder (tetapi ukur kinerja Anda untuk mencari tahu) :
Setiap kali Anda melakukan penggantian pada String, objek String baru dibuat, karena String tidak dapat diubah. StringBuilder bisa berubah, artinya, dapat diubah sebanyak yang Anda inginkan.
sumber
StringBuilder
akan melakukan penggantian dengan lebih efisien, karena buffer array karakternya dapat ditentukan ke panjang yang diperlukan.StringBuilder
dirancang untuk lebih dari sekadar menambahkan!Tentu pertanyaan sebenarnya adalah apakah ini merupakan optimasi yang terlalu jauh? JVM sangat baik dalam menangani pembuatan beberapa objek dan pengumpulan sampah berikutnya, dan seperti semua pertanyaan pengoptimalan, pertanyaan pertama saya adalah apakah Anda telah mengukur ini dan menentukan bahwa ini adalah masalah.
sumber
Bagaimana jika menggunakan metode replaceAll () ?
sumber
str.replaceAll(search1, replace1).replaceAll(search2, replace2).replaceAll(search3, replace3).replaceAll(search4, replace4)
Rythm a java template engine sekarang dirilis dengan fitur baru yang disebut mode interpolasi String yang memungkinkan Anda melakukan sesuatu seperti:
Kasus di atas menunjukkan Anda dapat mengirimkan argumen ke templat berdasarkan posisi. Rythm juga memungkinkan Anda untuk menyampaikan argumen dengan nama:
Catatan Rythm SANGAT CEPAT, sekitar 2 hingga 3 kali lebih cepat dari format dan kecepatan String, karena ia mengkompilasi template ke dalam kode byte java, kinerja runtime sangat dekat dengan konsentrasi dengan StringBuilder.
Tautan:
sumber
"%cat% really needs some %beverage%.";
bukankah%
token yang dipisahkan itu merupakan format yang ditentukan sebelumnya? Poin pertama Anda lebih lucu lagi, JDK menyediakan banyak "kemampuan lama", beberapa di antaranya dimulai dari tahun 90-an, mengapa orang repot-repot menggunakannya? Komentar dan downvoting Anda tidak masuk akalDi bawah ini berdasarkan jawaban Todd Owen . Solusi tersebut memiliki masalah bahwa jika pengganti berisi karakter yang memiliki arti khusus dalam ekspresi reguler, Anda bisa mendapatkan hasil yang tidak diharapkan. Saya juga ingin dapat secara opsional melakukan penelusuran tidak peka huruf besar / kecil. Inilah yang saya dapatkan:
Berikut adalah kasus pengujian unit saya:
sumber
sumber
Periksa ini:
Misalnya:
sumber
Ringkasan: Implementasi kelas tunggal dari jawaban Dave, untuk secara otomatis memilih yang paling efisien dari dua algoritma.
Ini adalah implementasi kelas tunggal yang lengkap berdasarkan jawaban luar biasa dari Dave Jarvis di atas . Kelas secara otomatis memilih di antara dua algoritme yang disediakan berbeda, untuk efisiensi maksimum. (Jawaban ini untuk orang yang hanya ingin menyalin dan menempel dengan cepat.)
Kelas ReplaceStrings:
Dependensi Maven yang dibutuhkan:
(Tambahkan ini ke file pom Anda jika perlu.)
sumber