Bagaimana memastikan urutan pemrosesan dalam stream java8?

148

Saya ingin memproses daftar di dalam XMLobjek java. Saya harus memastikan memproses semua elemen agar saya menerimanya.

Haruskah karena itu saya memanggil sequentialsetiap streamsaya gunakan? list.stream().sequential().filter().forEach()

Atau apakah cukup menggunakan aliran saja selama saya tidak menggunakan paralelisme? list.stream().filter().forEach()

anggota
sumber

Jawaban:

339

Anda mengajukan pertanyaan yang salah. Anda bertanya tentang sequentialvs. parallelsedangkan Anda ingin memproses item dalam urutan , jadi Anda harus bertanya tentang pemesanan . Jika Anda memiliki aliran yang dipesan dan melakukan operasi yang menjamin untuk mempertahankan pesanan, tidak masalah apakah aliran diproses secara paralel atau berurutan; implementasi akan mempertahankan pesanan.

Properti yang dipesan berbeda dari paralel vs berurutan. Misalnya jika Anda menelepon stream()pada HashSetsungai akan unordered sambil menelepon stream()pada Listpengembalian aliran memerintahkan. Perhatikan bahwa Anda dapat menelepon unordered()untuk melepaskan kontrak pemesanan dan berpotensi meningkatkan kinerja. Setelah aliran tidak memiliki pemesanan, tidak ada cara untuk membangun kembali pemesanan. (Satu-satunya cara untuk mengubah aliran yang tidak berurutan menjadi yang dipesan adalah dengan menelepon sorted, namun, pesanan yang dihasilkan belum tentu urutan asli).

Lihat juga bagian "Memesan" dari java.util.streamdokumentasi paket .

Untuk memastikan pemeliharaan pemesanan di seluruh operasi aliran, Anda harus mempelajari dokumentasi sumber aliran, semua operasi menengah dan operasi terminal untuk apakah mereka mempertahankan pesanan atau tidak (atau apakah sumber memiliki pemesanan di urutan pertama). tempat).

Ini bisa sangat halus, misalnya Stream.iterate(T,UnaryOperator)membuat aliran yang diurutkan sementara Stream.generate(Supplier)membuat aliran yang tidak terurut . Perhatikan bahwa Anda juga membuat kesalahan umum dalam pertanyaan Anda karena tidak mempertahankan pemesanan. Anda harus menggunakan jika Anda ingin memproses elemen aliran dalam urutan yang terjamin.forEach forEachOrdered

Jadi jika listpertanyaan Anda adalah a java.util.List, stream()metodenya akan mengembalikan aliran yang dipesan dan filtertidak akan mengubah pemesanan. Jadi jika Anda memanggil list.stream().filter() .forEachOrdered(), semua elemen akan diproses secara berurutan, sedangkan untuk list.parallelStream().filter().forEachOrdered()elemen mungkin diproses secara paralel (misalnya dengan filter) tetapi tindakan terminal masih akan dipanggil secara berurutan (yang jelas akan mengurangi manfaat dari eksekusi paralel) .

Jika Anda, misalnya, gunakan operasi seperti

List<…> result=inputList.parallelStream().map(…).filter(…).collect(Collectors.toList());

seluruh operasi mungkin mendapat manfaat dari eksekusi paralel tetapi daftar yang dihasilkan akan selalu dalam urutan yang benar, terlepas dari apakah Anda menggunakan aliran paralel atau berurutan.

Holger
sumber
48
Ya, jawaban yang bagus. Satu hal yang saya temukan adalah bahwa terminologi yang kita gunakan, setidaknya dalam bahasa Inggris, seperti "sebelum," "sesudah," dan seterusnya, cukup ambigu. Ada dua jenis pemesanan di sini: 1) pertemuan pesanan (juga dikenal sebagai tatanan spasial ), dan 2) pemrosesan pesanan (juga dikenal sebagai tatanan sementara ). Dengan perbedaan ini dalam pikiran mungkin akan membantu untuk menggunakan kata-kata seperti "kiri" atau "kanan" ketika membahas urutan pertemuan dan "lebih awal dari" atau "lebih lambat dari" ketika membahas urutan pemrosesan.
Stuart Marks
Saya mengerti List<>akan mempertahankan pesanan, tetapi akankah Collection<>?
Josh C.
5
@JoshC. itu tergantung pada jenis koleksi yang sebenarnya. SetBiasanya tidak, kecuali itu adalah SortedSetatau LinkedHashSet. Pandangan koleksi dari Map( keySet(),, entrySet()dan values()) mewarisi Mapkebijakan, yaitu dipesan ketika peta adalah SortedMapatau LinkedHashMap. Perilaku ditentukan oleh karakteristik yang dilaporkan oleh spliterator koleksi . The defaultpelaksanaan Collectiontidak melaporkan ORDEREDkarakteristik, sehingga unordered, kecuali ditimpa.
Holger
@ Holger Saya punya pertanyaan yang mungkin agak terkait dengan bagian kecil dari jawaban Anda.
Naman
1
Patut dicatat bahwa forEachOrderedhanya berbeda forEachketika menggunakan aliran paralel - tetapi praktik yang baik untuk tetap menggunakannya saat memesan masalah jika metode pengukusan berubah ...
Steve Chambers
0

Pendeknya:

Pemesanan tergantung pada struktur data sumber dan operasi aliran perantara. Dengan asumsi Anda menggunakan Listpemrosesan harus dipesan (karena filtertidak akan mengubah urutan di sini).

Keterangan lebih lanjut:

Sequential vs Parallel vs Unordered:

Javadocs

S sequential()
Returns an equivalent stream that is sequential. May return itself, either because the stream was already sequential, or because the underlying stream state was modified to be sequential.
This is an intermediate operation.
S parallel()
Returns an equivalent stream that is parallel. May return itself, either because the stream was already parallel, or because the underlying stream state was modified to be parallel.
This is an intermediate operation.
S unordered()
Returns an equivalent stream that is unordered. May return itself, either because the stream was already unordered, or because the underlying stream state was modified to be unordered.
This is an intermediate operation.

Pengurutan Aliran:

Javadocs

Streaming mungkin atau mungkin tidak memiliki urutan pertemuan yang ditentukan. Apakah aliran memiliki urutan pertemuan atau tidak tergantung pada sumber dan operasi perantara. Sumber aliran tertentu (seperti Daftar atau array) secara intrinsik dipesan, sedangkan yang lain (seperti HashSet) tidak. Beberapa operasi perantara, seperti diurutkan (), dapat memaksakan urutan pertemuan pada aliran yang tidak berurutan, dan yang lain dapat membuat aliran yang dipesan tidak berurutan, seperti BaseStream.unordered (). Lebih jauh, beberapa operasi terminal mungkin mengabaikan urutan pertemuan, seperti forEach ().

Jika aliran diperintahkan, sebagian besar operasi dibatasi untuk beroperasi pada elemen dalam urutan pertemuannya; jika sumber stream adalah Daftar yang berisi [1, 2, 3], maka hasil dari mengeksekusi peta (x -> x * 2) harus [2, 4, 6]. Namun, jika sumber tidak memiliki urutan pertemuan yang ditentukan, maka permutasi nilai apa pun [2, 4, 6] akan menjadi hasil yang valid.

Untuk aliran berurutan, ada atau tidaknya urutan pertemuan tidak mempengaruhi kinerja, hanya determinisme. Jika aliran dipesan, eksekusi berulang dari pipa aliran identik pada sumber yang identik akan menghasilkan hasil yang identik; jika tidak dipesan, eksekusi berulang mungkin menghasilkan hasil yang berbeda.

Untuk aliran paralel, melonggarkan kendala pemesanan terkadang dapat memungkinkan eksekusi yang lebih efisien. Operasi agregat tertentu, seperti memfilter duplikat (berbeda ()) atau reduksi yang dikelompokkan (Collectors.groupingBy ()) dapat diimplementasikan lebih efisien jika pemesanan elemen tidak relevan. Demikian pula, operasi yang secara intrinsik terkait dengan order, seperti limit (), mungkin memerlukan buffering untuk memastikan pemesanan yang tepat, merusak manfaat paralelisme. Dalam kasus di mana streaming memiliki urutan pertemuan, tetapi pengguna tidak terlalu peduli tentang urutan pertemuan itu, secara eksplisit menghapus urutan aliran dengan unordered () dapat meningkatkan kinerja paralel untuk beberapa operasi stateful atau terminal. Namun, sebagian besar aliran pipa, seperti "jumlah bobot blok" contoh di atas,

Saikat
sumber