Mana cara paling efisien untuk melintasi koleksi?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
atau
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Harap dicatat, bahwa ini bukan duplikat persis dari ini , ini , ini , atau ini , meskipun salah satu jawaban untuk pertanyaan terakhir sudah dekat. Alasan bahwa ini bukan dupe, adalah bahwa sebagian besar membandingkan loop di mana Anda memanggil get(i)
di dalam loop, daripada menggunakan iterator.
Seperti yang disarankan di Meta saya akan memposting jawaban saya untuk pertanyaan ini.
java
collections
foreach
Paul Wagland
sumber
sumber
Jawaban:
Jika Anda hanya berkeliaran di koleksi untuk membaca semua nilai, maka tidak ada perbedaan antara menggunakan iterator atau sintaks loop baru, karena sintaks baru hanya menggunakan iterator bawah air.
Namun, jika Anda maksud dengan loop loop "c-style" lama:
Maka yang baru untuk loop, atau iterator, bisa menjadi jauh lebih efisien, tergantung pada struktur data yang mendasarinya. Alasan untuk ini adalah bahwa untuk beberapa struktur data,
get(i)
adalah operasi O (n), yang membuat loop operasi O (n 2 ). Daftar tertaut tradisional adalah contoh dari struktur data tersebut. Semua iterator memiliki sebagai persyaratan mendasar itunext()
harus menjadi operasi O (1), membuat loop O (n).Untuk memverifikasi bahwa iterator digunakan di bawah air oleh sintaks loop baru, bandingkan kode bytec yang dihasilkan dari dua snipet Java berikut. Pertama untuk for:
Dan kedua, iterator:
Seperti yang Anda lihat, kode byte yang dihasilkan identik secara efektif, sehingga tidak ada penalti performa untuk menggunakan salah satu form. Oleh karena itu, Anda harus memilih bentuk loop yang paling menarik bagi Anda, bagi kebanyakan orang yang akan menjadi untuk-setiap loop, karena memiliki kode boilerplate yang lebih sedikit.
sumber
for(int i; i < list.size(); i++) {
gaya lingkaran juga harus mengevaluasilist.size()
pada akhir setiap iterasi, jika digunakan kadang-kadang lebih efisien untuk men-cache hasillist.size()
pertama.Perbedaannya bukan pada kinerja, tetapi dalam kemampuan. Saat menggunakan referensi secara langsung, Anda memiliki kekuasaan lebih besar atas penggunaan jenis iterator secara eksplisit (mis. List.iterator () vs. List.listIterator (), meskipun dalam kebanyakan kasus mereka mengembalikan implementasi yang sama). Anda juga memiliki kemampuan untuk mereferensikan Iterator di loop Anda. Ini memungkinkan Anda untuk melakukan hal-hal seperti menghapus item dari koleksi Anda tanpa mendapatkan ConcurrentModificationException.
misalnya
Ini bagus:
Ini bukan, karena akan mengeluarkan pengecualian modifikasi bersamaan:
sumber
Untuk memperluas jawaban Paul sendiri, ia telah menunjukkan bahwa bytecode sama pada kompiler tertentu (mungkin javac Sun?) Tetapi kompiler yang berbeda tidak dijamin untuk menghasilkan bytecode yang sama, kan? Untuk melihat perbedaan sebenarnya antara keduanya, mari langsung ke sumbernya dan periksa Spesifikasi Bahasa Jawa, khususnya 14.14.2, "Pernyataan yang disempurnakan" :
Dengan kata lain, diperlukan oleh JLS bahwa keduanya setara. Dalam teori yang bisa berarti perbedaan marginal dalam bytecode, tetapi pada kenyataannya peningkatan untuk loop diperlukan untuk:
.iterator()
metode ini.hasNext()
.next()
Jadi, dengan kata lain, untuk semua tujuan praktis, bytecode akan identik, atau hampir identik. Sulit untuk membayangkan implementasi kompiler yang akan menghasilkan perbedaan yang signifikan antara keduanya.
sumber
The
foreach
underhood adalah menciptakaniterator
, memanggil hasNext () dan memanggil berikutnya () untuk mendapatkan nilai; Masalah dengan kinerja hanya muncul jika Anda menggunakan sesuatu yang mengimplementasikan RandomomAccess.Masalah kinerja dengan loop berbasis iterator adalah karena:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
selama setiap iterasi dari loop ada panggilan virtual invokeInterface (pergi melalui semua kelas, lalu lakukan metode tabel pencarian sebelum melompat).hasNext()
angka panggilan nilainya: # 1 mendapatkan jumlah saat ini dan # 2 mendapatkan jumlah totaliter.next
(jadi: pergi melalui semua kelas dan melakukan pencarian tabel metode sebelum melompat) dan juga harus melakukan pencarian bidang: # 1 mendapatkan indeks dan # 2 mendapatkan referensi ke array untuk melakukan offset ke dalamnya (di setiap iterasi).Optimisasi potensial adalah beralih ke
index iteration
dengan pencarian ukuran cache:Di sini kita memiliki:
customList.size()
pada pembuatan awal for loop untuk mendapatkan ukurancustomList.get(x)
selama body for loop, yang merupakan bidang pencarian ke array dan kemudian dapat melakukan offset ke dalam arrayKami mengurangi satu ton panggilan metode, pencarian bidang. Ini yang tidak ingin Anda lakukan dengan
LinkedList
atau dengan sesuatu yang bukan objekRandomAccess
koleksi, jika tidak makacustomList.get(x)
akan berubah menjadi sesuatu yang harus dilaluiLinkedList
pada setiap iterasi.Ini sempurna ketika Anda tahu bahwa itu adalah
RandomAccess
kumpulan daftar berdasarkan.sumber
foreach
tetap menggunakan iterators di bawah tenda. Ini benar-benar hanya gula sintaksis.Pertimbangkan program berikut:
Mari kita kompilasi dengan
javac Whatever.java
,Dan baca bytecode dibongkar
main()
, menggunakanjavap -c Whatever
:Kita dapat melihat bahwa
foreach
kompilasi ke sebuah program yang:List.iterator()
Iterator.hasNext()
: memanggilIterator.next()
dan melanjutkan loopAdapun "mengapa loop tidak berguna ini dioptimalkan dari kode yang dikompilasi? Kita dapat melihat bahwa itu tidak melakukan apa-apa dengan item daftar": baik, mungkin bagi Anda untuk kode iterable Anda sehingga
.iterator()
memiliki efek samping , atau lebih yang.hasNext()
memiliki efek samping atau konsekuensi yang berarti.Anda dapat dengan mudah membayangkan bahwa iterable yang mewakili permintaan yang dapat digulir dari database mungkin melakukan sesuatu yang dramatis
.hasNext()
(seperti menghubungi database, atau menutup kursor karena Anda telah mencapai akhir set hasil).Jadi, meskipun kita dapat membuktikan bahwa tidak ada yang terjadi dalam loop body ... itu lebih mahal (sulit?) Untuk membuktikan bahwa tidak ada yang berarti / konsekuensi yang terjadi ketika kita mengulanginya. Kompilator harus meninggalkan tubuh loop kosong ini dalam program.
Yang terbaik yang bisa kami harapkan adalah peringatan kompiler . Sungguh menarik bahwa
javac -Xlint:all Whatever.java
tidak tidak memperingatkan kita tentang tubuh lingkaran kosong ini. IntelliJ IDEA melakukannya. Memang saya telah mengkonfigurasi IntelliJ untuk menggunakan Eclipse Compiler, tapi itu mungkin bukan alasan mengapa.sumber
Iterator adalah antarmuka dalam kerangka Java Collections yang menyediakan metode untuk melintasi atau beralih pada koleksi.
Baik iterator dan for loop bertindak serupa ketika motif Anda adalah hanya melintasi koleksi untuk membaca elemen-elemennya.
for-each
hanyalah satu cara untuk beralih ke Koleksi.Sebagai contoh:
Dan untuk-setiap loop hanya dapat digunakan pada objek yang mengimplementasikan antarmuka iterator.
Sekarang kembali ke case for loop dan iterator.
Perbedaannya muncul ketika Anda mencoba mengubah koleksi. Dalam hal ini, iterator lebih efisien karena sifatnya yang gagal cepat . yaitu. ia memeriksa setiap modifikasi dalam struktur koleksi yang mendasari sebelum mengulangi elemen berikutnya. Jika ada modifikasi yang ditemukan, itu akan membuang ConcurrentModificationException .
(Catatan: Fungsionalitas iterator ini hanya berlaku dalam kasus kelas koleksi di paket java.util. Ini tidak berlaku untuk koleksi bersamaan karena sifatnya gagal-aman secara alami)
sumber
Kita harus menghindari penggunaan tradisional untuk loop saat bekerja dengan Koleksi. Alasan sederhana yang akan saya berikan adalah bahwa kompleksitas untuk loop adalah urutan O (sqr (n)) dan kompleksitas Iterator atau bahkan peningkatan untuk loop hanya O (n). Jadi itu memberikan perbedaan kinerja .. Hanya mengambil daftar sekitar 1000 item dan mencetaknya menggunakan kedua cara. dan juga mencetak perbedaan waktu untuk eksekusi. Anda dapat melihat perbedaannya.
sumber