1. Gambaran keseluruhan
Dalam artikel ini, kita akan melihat pelbagai cara untuk mencari array untuk nilai yang ditentukan.
Kami juga akan membandingkan bagaimana performanya menggunakan JMH (Java Microbenchmark Harness) untuk menentukan kaedah mana yang paling sesuai.
2. Persediaan
Untuk contoh kami, kami akan menggunakan pelbagai yang mengandungi dijana secara rawak Strings untuk setiap ujian:
String[] seedArray(int length) { String[] strings = new String[length]; Random value = new Random(); for (int i = 0; i < length; i++) { strings[i] = String.valueOf(value.nextInt()); } return strings; }
Untuk menggunakan semula array di setiap tanda aras, kami akan menyatakan kelas dalaman untuk menahan susunan dan kiraan sehingga kami dapat menyatakan ruang lingkupnya untuk JMH:
@State(Scope.Benchmark) public static class SearchData { static int count = 1000; static String[] strings = seedArray(1000); }
3. Pencarian Asas
Tiga kaedah yang biasa digunakan untuk mencari lokasi di atas adalah seperti yang List, yang Set, atau dengan gelung yang mengkaji setiap ahli sehingga ia mendapati perlawanan.
Mari kita mulakan dengan tiga kaedah yang menerapkan setiap algoritma:
boolean searchList(String[] strings, String searchString) { return Arrays.asList(SearchData.strings) .contains(searchString); } boolean searchSet(String[] strings, String searchString) { Set stringSet = new HashSet(Arrays.asList(SearchData.strings)); return stringSet.contains(searchString); } boolean searchLoop(String[] strings, String searchString) { for (String string : SearchData.strings) { if (string.equals(searchString)) return true; } return false; }
Kami akan menggunakan anotasi kelas ini untuk memberitahu JMH untuk menghasilkan masa purata dalam mikrodetik dan menjalankan lima lelaran pemanasan untuk memastikan bahawa ujian kami boleh dipercayai:
@BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5) @OutputTimeUnit(TimeUnit.MICROSECONDS)
Dan jalankan setiap ujian dalam satu gelung:
@Benchmark public void searchArrayLoop() { for (int i = 0; i < SearchData.count; i++) { searchLoop(SearchData.strings, "T"); } } @Benchmark public void searchArrayAllocNewList() { for (int i = 0; i < SearchData.count; i++) { searchList(SearchData.strings, "T"); } } @Benchmark public void searchArrayAllocNewSet() { for (int i = 0; i < SearchData.count; i++) { searchSet(SearchData.strings, "S"); } }
Apabila kami menjalankan dengan 1000 carian untuk setiap kaedah, hasil kami kelihatan seperti ini:
SearchArrayTest.searchArrayAllocNewList avgt 20 937.851 ± 14.226 us/op SearchArrayTest.searchArrayAllocNewSet avgt 20 14309.122 ± 193.844 us/op SearchArrayTest.searchArrayLoop avgt 20 758.060 ± 9.433 us/op
Pencarian gelung lebih cekap daripada yang lain. Tetapi ini sebahagiannya disebabkan oleh cara kita menggunakan koleksi.
Kami membuat contoh Senarai baru dengan setiap panggilan ke searchList () dan Senarai baru dan HashSet baru dengan setiap panggilan ke searchSet () . Membuat objek ini akan menghasilkan kos tambahan yang tidak dapat dilakukan oleh looping.
4. Pencarian Lebih Efisien
Apa yang berlaku apabila kita membuat satu contoh Daftar dan Set dan kemudian menggunakannya semula untuk setiap carian?
Mari mencubanya:
public void searchArrayReuseList() { List asList = Arrays.asList(SearchData.strings); for (int i = 0; i < SearchData.count; i++) { asList.contains("T"); } } public void searchArrayReuseSet() { Set asSet = new HashSet(Arrays.asList(SearchData.strings)); for (int i = 0; i < SearchData.count; i++) { asSet.contains("T"); } }
Kami akan menjalankan kaedah ini dengan anotasi JMH yang sama seperti di atas, dan menyertakan hasil untuk gelung sederhana untuk perbandingan.
Kami melihat hasil yang sangat berbeza:
SearchArrayTest.searchArrayLoop avgt 20 758.060 ± 9.433 us/op SearchArrayTest.searchArrayReuseList avgt 20 837.265 ± 11.283 us/op SearchArrayTest.searchArrayReuseSet avgt 20 14.030 ± 0.197 us/op
Semasa mencari Senarai sedikit lebih cepat daripada sebelumnya, Set turun menjadi kurang dari 1 peratus masa yang diperlukan untuk gelung!
Sekarang setelah kami membuang masa yang diperlukan untuk membuat Koleksi baru dari setiap carian, hasil ini masuk akal.
Mencari jadual hash, struktur yang mendasari HashSet , mempunyai kerumitan waktu 0 (1), sementara array, yang mendasari ArrayList adalah 0 (n).
5. Pencarian Binari
Kaedah lain untuk mencari array adalah carian binari. Walaupun sangat cekap, carian binari memerlukan susunan disusun terlebih dahulu.
Mari urutkan susunan dan cuba carian binari:
@Benchmark public void searchArrayBinarySearch() { Arrays.sort(SearchData.strings); for (int i = 0; i < SearchData.count; i++) { Arrays.binarySearch(SearchData.strings, "T"); } }
SearchArrayTest.searchArrayBinarySearch avgt 20 26.527 ± 0.376 us/op
Pencarian binari sangat pantas, walaupun kurang efisien daripada HashSet: prestasi terburuk untuk carian binari adalah 0 (log n), yang meletakkan prestasinya antara carian array dan jadual hash.
6. Kesimpulannya
Kami telah melihat beberapa kaedah mencari melalui pelbagai.
Berdasarkan hasil kami, HashSet berfungsi paling baik untuk mencari melalui senarai nilai. Walau bagaimanapun, kita perlu membuatnya terlebih dahulu dan menyimpannya di Set.
Seperti biasa, kod sumber penuh contoh terdapat di GitHub.