Array atau Daftar di Jawa. Mana yang lebih cepat?

351

Saya harus menyimpan ribuan string dalam memori agar dapat diakses secara serial di Jawa. Haruskah saya menyimpannya dalam array atau haruskah saya menggunakan semacam Daftar?

Karena array menyimpan semua data dalam memori yang berdekatan (tidak seperti Daftar), apakah penggunaan array untuk menyimpan ribuan string menyebabkan masalah?

euphoria83
sumber
5
"Karena array menyimpan semua data dalam memori yang berdekatan" apakah Anda memiliki kutipan untuk mendukung Java?
matt b
1
Tidak matt Saya tahu ini untuk C. Saya kira Java akan menggunakan metode yang sama.
euphoria83
Saya ragu itu akan membuat mereka dalam satu memori.
Fortyrunner
3
Bahkan jika itu adalah satu blok memori, masih hanya bernilai sekitar 1000 * 4 = 4kb, yang tidak banyak memori.
CookieOfFortune
3
@ mattb Itulah arti 'array' di seluruh CS. Tidak perlu mengutip. Banyak referensi dalam JLS dan [JVM Spec] () untuk panjang array hanya dapat dipahami jika array berdekatan.
Marquis of Lorne

Jawaban:

358

Saya sarankan Anda menggunakan profiler untuk menguji mana yang lebih cepat.

Pendapat pribadi saya adalah bahwa Anda harus menggunakan Daftar.

Saya bekerja pada basis kode besar dan sekelompok pengembang sebelumnya menggunakan array di mana-mana . Itu membuat kode sangat tidak fleksibel. Setelah mengubah bongkahan besar itu menjadi Daftar, kami perhatikan tidak ada perbedaan dalam kecepatan.

Fortyrunner
sumber
2
@Fortyrunner - Dari pengalaman Anda, apakah ada pilihan seperti itu di Jawa antara abstraksi dan formulir data mentah yang membuat perbedaan signifikan dalam kinerja?
euphoria83
4
Salah satu masalah dengan pengukuran kinerja adalah Anda harus terus menguji ulang terhadap versi Java yang baru. Saya sedang mengerjakan masalah saat ini ketika seseorang menggunakan int untuk kunci di peta (untuk menghemat ruang / waktu). Kita sekarang perlu mengubah semua garis ke objek baru - itu menyakitkan.
Fortyrunner
9
Jadi .. Saya sekarang mencoba dan menjauh dari data mentah. Jarang membuat perbedaan nyata. Hotspot adalah teknologi yang luar biasa dan Anda tidak boleh mencoba dan menebak kedua. Cobalah untuk menulis kode yang sederhana dan dapat dipelihara dan Hotspot akan melakukan sisanya.
Fortyrunner
4
Ingat bahwa hasil profiler hanya valid untuk platform Java yang Anda gunakan untuk melawan profiler. Yang mungkin berbeda dari pelanggan Anda.
Mikkel Løkke
4
Java yang efektif merekomendasikan Daftar karena mereka membantu dengan interoperabilitas API, dan juga lebih aman dengan keamanan tipe.
juanmf
164

Cara Java adalah Anda harus mempertimbangkan abstraksi data apa yang paling sesuai dengan kebutuhan Anda. Ingat bahwa di Java Daftar adalah abstrak, bukan tipe data konkret. Anda harus mendeklarasikan string sebagai Daftar, dan kemudian menginisialisasi menggunakan implementasi ArrayList.

List<String> strings = new ArrayList<String>();

Pemisahan Tipe Data Abstrak dan implementasi spesifik ini adalah salah satu aspek kunci dari pemrograman berorientasi objek.

ArrayList mengimplementasikan Daftar Tipe Data Abstrak menggunakan array sebagai implementasi yang mendasarinya. Kecepatan akses hampir identik dengan array, dengan keuntungan tambahan karena dapat menambah dan mengurangi elemen ke Daftar (meskipun ini adalah operasi O (n) dengan ArrayList) dan bahwa jika Anda memutuskan untuk mengubah implementasi yang mendasarinya nanti kamu bisa. Misalnya, jika Anda menyadari bahwa Anda memerlukan akses yang disinkronkan, Anda dapat mengubah implementasinya menjadi Vektor tanpa menulis ulang semua kode Anda.

Bahkan, ArrayList secara khusus dirancang untuk menggantikan susunan array tingkat rendah di sebagian besar konteks. Jika Java sedang dirancang hari ini, sangat mungkin bahwa array akan ditinggalkan sama sekali demi konstruk ArrayList.

Karena array menyimpan semua data dalam memori yang berdekatan (tidak seperti Daftar), apakah penggunaan array untuk menyimpan ribuan string menyebabkan masalah?

Di Jawa, semua koleksi hanya menyimpan referensi ke objek, bukan objek itu sendiri. Baik array dan ArrayList akan menyimpan beberapa ribu referensi dalam array yang berdekatan, sehingga pada dasarnya identik. Anda dapat mempertimbangkan bahwa blok yang berdekatan dari beberapa ribu referensi 32-bit akan selalu tersedia pada perangkat keras modern. Ini tidak menjamin bahwa Anda tidak akan kehabisan memori sama sekali, tentu saja, hanya saja kebutuhan memori yang berdekatan tidak sulit untuk terpenuhi.

cygil
sumber
Menambahkan tentu saja mungkin melibatkan realokasi array backing, jadi jika kinerja penting dan ukuran array diketahui sebelumnya, orang harus mempertimbangkan untuk menggunakan ArrayList # sureCapacity.
JesperE
6
Apakah Anda tidak membayar biaya pengikatan dinamis di sini?
Uri
2
Saya kira menambahkan bukan O (n) di ArrayList, harus ada beberapa efek ammortisasi saat menambahkan lebih dari sekali, misalnya kapasitas dua kali lipat alih-alih meningkat hanya dengan 1.
zedoo
@zedoo Saya pikir mereka berarti menambah dan mengurangi di tengah.
MalcolmOcean
"Jika Java sedang dirancang hari ini, sangat mungkin bahwa array akan ditinggalkan sama sekali demi konstruksi ArrayList." ... Aku benar-benar ragu kalau ini benar. Jika itu adalah JVM yang ditulis ulang hari ini, maka apa yang Anda katakan tentu saja mungkin. Tetapi dengan JVM yang kita miliki, array adalah tipe dasar di Jawa.
scottb
100

Meskipun jawaban yang mengusulkan untuk menggunakan ArrayList masuk akal di sebagian besar skenario, pertanyaan sebenarnya tentang kinerja relatif belum benar-benar dijawab.

Ada beberapa hal yang dapat Anda lakukan dengan array:

  • menciptakannya
  • mengatur item
  • dapatkan item
  • klon / salin

Kesimpulan umum

Meskipun operasi mengatur dan mendapatkan agak lebih lambat pada ArrayList (resp. 1 dan 3 nanosecond per panggilan pada mesin saya), ada sangat sedikit overhead menggunakan ArrayList vs array untuk penggunaan non-intensif. Namun ada beberapa hal yang perlu diingat:

  • mengubah ukuran operasi pada daftar (saat memanggil list.add(...)) mahal dan orang harus mencoba mengatur kapasitas awal pada tingkat yang memadai bila memungkinkan (perhatikan bahwa masalah yang sama muncul ketika menggunakan array)
  • ketika berhadapan dengan primitif, array dapat secara signifikan lebih cepat karena mereka akan memungkinkan seseorang untuk menghindari banyak konversi tinju / unboxing
  • aplikasi yang hanya mendapat / menetapkan nilai dalam ArrayList (tidak terlalu umum!) dapat melihat peningkatan kinerja lebih dari 25% dengan beralih ke array

Hasil terperinci

Berikut adalah hasil yang saya ukur untuk ketiga operasi menggunakan perpustakaan pembandingan jmh (kali dalam nanodetik) dengan JDK 7 pada mesin desktop x86 standar. Perhatikan bahwa ArrayList tidak pernah diubah ukurannya dalam pengujian untuk memastikan hasil yang sebanding. Kode benchmark tersedia di sini .

Pembuatan Array / ArrayList

Saya menjalankan 4 tes, menjalankan pernyataan berikut:

  • buatArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • buatArray10000: Integer[] array = new Integer[10000];
  • buatList10000: List<Integer> list = new ArrayList<> (10000);

Hasil (dalam nanodetik per panggilan, kepercayaan 95%):

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

Kesimpulan: tidak ada perbedaan yang nyata .

dapatkan operasi

Saya menjalankan 2 tes, menjalankan pernyataan berikut:

  • getList: return list.get(0);
  • getArray: return array[0];

Hasil (dalam nanodetik per panggilan, kepercayaan 95%):

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

Kesimpulan: mendapatkan dari array sekitar 25% lebih cepat daripada dari ArrayList, meskipun perbedaannya hanya pada urutan satu nanosecond.

mengatur operasi

Saya menjalankan 2 tes, menjalankan pernyataan berikut:

  • setList: list.set(0, value);
  • setArray: array[0] = value;

Hasil (dalam nanodetik per panggilan):

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

Kesimpulan: operasi set pada array sekitar 40% lebih cepat dari pada daftar, tetapi, seperti untuk mendapatkan, setiap operasi set membutuhkan beberapa nanodetik - jadi untuk perbedaan untuk mencapai 1 detik, seseorang perlu mengatur item dalam daftar / array ratusan jutaan kali!

klon / salin

Mendelegasikan copy constructor ArrayList ke Arrays.copyOfsehingga kinerja identik dengan copy array (menyalin array melalui clone, Arrays.copyOfatau System.arrayCopy tidak membuat perbedaan materi yang berarti ).

assylias
sumber
1
Analisis yang bagus. Namun, sehubungan dengan komentar Anda "ketika berhadapan dengan primitif, array dapat secara signifikan lebih cepat karena mereka akan memungkinkan seseorang untuk menghindari banyak konversi tinju / unboxing", Anda dapat memiliki kue dan memakannya juga, dengan Daftar yang didukung array primitif-array yang didukung penerapan; misalnya: github.com/scijava/scijava-common/blob/master/src/main/java/org/… . Saya sebenarnya cukup terkejut hal seperti itu belum berhasil menjadi inti Java.
ctrueden
2
@ctrueden ya komentar diterapkan pada standar ArrayList JDK. trove4j adalah perpustakaan terkenal yang mendukung daftar primitif. Java 8 membawa beberapa peningkatan dengan beberapa stream khusus-primitif.
assylias
Saya tidak tahu bagaimana tolok ukur jmh bekerja tetapi apakah mereka memperhitungkan kompilasi JIT yang dapat terjadi? Kinerja aplikasi Java dapat bervariasi dari waktu ke waktu karena JVM mengkompilasi kode Anda.
Hoffmann
@Offoff Ya - ini termasuk fase pemanasan yang dikeluarkan dari pengukuran.
assylias
97

Anda harus memilih tipe generik daripada array. Seperti yang disebutkan oleh orang lain, array tidak fleksibel dan tidak memiliki kekuatan ekspresif tipe generik. (Namun mereka mendukung pemeriksaan ketik runtime, tetapi itu bercampur dengan buruk dengan tipe generik.)

Tetapi, seperti biasa, ketika mengoptimalkan Anda harus selalu mengikuti langkah-langkah ini:

  • Jangan optimalkan sampai Anda memiliki versi kode yang bagus, bersih, dan berfungsi . Mengubah ke tipe generik bisa sangat termotivasi pada langkah ini.
  • Ketika Anda memiliki versi yang bagus dan bersih, putuskan apakah itu cukup cepat.
  • Jika tidak cukup cepat, ukur kinerjanya . Langkah ini penting karena dua alasan. Jika Anda tidak mengukur, Anda tidak akan (1) tahu dampak dari setiap optimasi yang Anda buat dan (2) tahu di mana harus mengoptimalkan.
  • Optimalkan bagian terpanas dari kode Anda.
  • Ukur lagi. Ini sama pentingnya dengan mengukur sebelumnya. Jika optimasi tidak memperbaiki keadaan, kembalikan . Ingat, kode tanpa optimasi itu bersih, baik, dan berfungsi.
JesperE
sumber
24

Saya menduga poster asli berasal dari latar belakang C ++ / STL yang menyebabkan beberapa kebingungan. Dalam C ++ std::listadalah daftar tertaut ganda.

Di Jawa [java.util.]Listadalah antarmuka bebas implementasi (kelas abstrak murni dalam istilah C ++). Listbisa menjadi daftar yang ditautkan dua kali lipat - java.util.LinkedListdisediakan. Namun, 99 kali dari 100 saat Anda ingin membuat yang baru List, Anda ingin menggunakannya java.util.ArrayList, yang merupakan padanan kasar dari C ++ std::vector. Ada implementasi standar lainnya, seperti yang dikembalikan oleh java.util.Collections.emptyList()dan java.util.Arrays.asList().

Dari sudut pandang kinerja ada hit kecil dari harus melalui antarmuka dan objek tambahan, namun runtime inlining berarti ini jarang memiliki makna. Juga ingat bahwa Stringbiasanya objek plus array. Jadi untuk setiap entri, Anda mungkin memiliki dua objek lainnya. Dalam C ++ std::vector<std::string>, meskipun menyalin dengan nilai tanpa pointer seperti itu, array karakter akan membentuk objek untuk string (dan ini biasanya tidak akan dibagikan).

Jika kode khusus ini benar-benar peka terhadap kinerja, Anda bisa membuat char[]larik tunggal (atau bahkan byte[]) untuk semua karakter dari semua string, dan kemudian larik offset. IIRC, ini adalah bagaimana javac diimplementasikan.

Tom Hawtin - tackline
sumber
1
Terima kasih untuk jawabannya. Tapi tidak, saya tidak membingungkan daftar C ++ dengan Daftar antarmuka Java. Saya mengajukan pertanyaan sedemikian rupa karena saya ingin membandingkan kinerja implementasi Daftar seperti ArrayList dan Vector dengan array mentah.
euphoria83
ArrayList dan Vector "menyimpan semua data dalam memori yang berdekatan".
Tom Hawtin - tackline
13

Saya setuju bahwa dalam kebanyakan kasus Anda harus memilih fleksibilitas dan keanggunan ArrayLists daripada array - dan dalam banyak kasus dampak terhadap kinerja program akan diabaikan.

Namun, jika Anda melakukan iterasi yang konstan dan berat dengan sedikit perubahan struktural (tanpa penambahan dan pemindahan) untuk, katakanlah, perenderan grafik perangkat lunak atau mesin virtual kustom, tes pembandingan akses berurutan saya menunjukkan bahwa ArrayLists 1,5x lebih lambat daripada array pada saya. sistem (Java 1.6 pada iMac saya yang berumur satu tahun).

Beberapa kode:

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}
AbePralle
sumber
Saya menemukan ini jawaban yang menarik, tapi saya ingin tahu apakah ini lebih buruk lagi jika ArrayList tidak diinisialisasi dengan ukuran awal dalam memori. Secara umum manfaat menggunakan ArrayList di atas array asli dalam arti adalah bahwa Anda tidak akan tahu dan Anda tidak perlu khawatir. ArrayLists secara default dibuat dengan panjang awal 10 dan kemudian diubah ukurannya. Saya pikir ukurannya mahal. Saya belum mencoba membandingkannya dengan jelas.
Zak Patterson
4
Patokan mikro ini memiliki kelemahan (tidak ada pemanasan, operasi tidak dalam metode terpisah sehingga bagian arraylist tidak pernah dioptimalkan oleh JIT dll.)
assylias
Saya setuju dengan para assylias. Hasil dari tolok ukur ini tidak boleh dipercaya.
Stephen C
@StephenC Saya telah menambahkan patokan mikro yang tepat (yang menunjukkan bahwa mendapatkan operasi sebanding).
assylias
11

Yah pertama-tama perlu diperjelas maksud Anda "daftar" dalam pengertian struktur data sci comp klasik (yaitu daftar tertaut) atau maksud Anda java.util.List? Jika maksud Anda adalah java.util.List, ini adalah antarmuka. Jika Anda ingin menggunakan array, cukup gunakan implementasi ArrayList dan Anda akan mendapatkan perilaku dan semantik seperti array. Masalah terpecahkan.

Jika maksud Anda array vs daftar tertaut, ini adalah argumen yang sedikit berbeda di mana kita kembali ke Big O (di sini adalah penjelasan bahasa Inggris yang sederhana jika ini adalah istilah yang tidak dikenal.

Himpunan;

  • Akses Acak: O (1);
  • Masukkan: O (n);
  • Hapus: O (n).

Daftar Tertaut:

  • Akses Acak: O (n);
  • Masukkan: O (1);
  • Hapus: O (1).

Jadi Anda memilih mana yang paling sesuai dengan cara Anda mengubah ukuran array Anda. Jika Anda mengubah ukuran, memasukkan dan menghapus banyak maka mungkin daftar yang ditautkan adalah pilihan yang lebih baik. Hal yang sama berlaku jika akses acak jarang. Anda menyebutkan akses serial. Jika Anda terutama melakukan akses serial dengan sedikit modifikasi maka mungkin tidak masalah yang Anda pilih.

Daftar tertaut memiliki overhead yang sedikit lebih tinggi karena, seperti yang Anda katakan, Anda sedang berhadapan dengan blok memori yang berpotensi tidak bersebelahan dan (efektif) pointer ke elemen berikutnya. Itu mungkin bukan faktor penting kecuali Anda berurusan dengan jutaan entri.

cletus
sumber
i mean java.util.List interface
euphoria83
1
Akses acak O (n) pada linkedlist sepertinya merupakan masalah besar bagi saya.
Bjorn
11

Saya menulis patokan kecil untuk membandingkan ArrayLists dengan Array. Di laptop lama saya, waktu untuk menelusuri daftar elemen 5000, 1000 kali, sekitar 10 milidetik lebih lambat dari kode array yang setara.

Jadi, jika Anda tidak melakukan apa-apa selain iterasi daftar, dan Anda banyak melakukannya, maka mungkin itu layak optimasi. Kalau tidak, aku akan menggunakan Daftar, karena itu akan membuat lebih mudah ketika Anda lakukan perlu mengoptimalkan kode.

nb Saya memang memperhatikan bahwa menggunakan for String s: stringsListadalah sekitar 50% lebih lambat daripada menggunakan gaya lama untuk-loop untuk mengakses daftar. Go figure ... Inilah dua fungsi yang saya tentukan waktunya; array dan daftar diisi dengan 5000 string acak (berbeda).

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}
Chris May
sumber
@ Chris May: Kerja bagus! Apa waktu berjalan aktual untuk keduanya? Bisakah Anda memberi tahu saya ukuran string yang Anda gunakan? Juga, karena penggunaan 'String s: stringsList' membuatnya lebih lama, ini adalah ketakutan utama saya dalam menggunakan abstraksi yang lebih tinggi di Jawa pada umumnya.
euphoria83
Tidak masalah berapa lama string untuk mcirobenchmark ini. Tidak ada gc, dan char[]tidak tersentuh (ini bukan C).
Tom Hawtin - tackline
Waktu yang khas untuk saya adalah ~ 25 ms untuk versi array, ~ 35 ms untuk versi ArrayList. Senar sepanjang 15-20 karakter. Seperti kata Tom, ukuran string tidak membuat banyak perbedaan, dengan string ~ 100-char timing-nya hampir sama.
Chris
3
Bagaimana Anda mengukur? Pengukuran naif dalam tolok ukur mikro Java biasanya menghasilkan lebih banyak informasi yang salah daripada informasi. Waspadalah terhadap pernyataan di atas.
jmg
6

Tidak, karena secara teknis, array hanya menyimpan referensi ke string. String sendiri dialokasikan di lokasi yang berbeda. Untuk seribu item, saya akan mengatakan daftar akan lebih baik, lebih lambat, tetapi menawarkan lebih banyak fleksibilitas dan lebih mudah digunakan, terutama jika Anda akan mengubah ukurannya.

CookieOfFortune
sumber
5
Daftar juga menyimpan referensi hanya untuk string.
Peter Štibraný
6

Jika Anda memiliki ribuan, pertimbangkan untuk menggunakan trie. Trie adalah struktur mirip pohon yang menggabungkan awalan umum dari string yang disimpan.

Misalnya, jika senarnya

intern
international
internationalize
internet
internets

Trie akan menyimpan:

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

String membutuhkan 57 karakter (termasuk terminator nol, '\ 0') untuk penyimpanan, ditambah ukuran objek String yang menahannya. (Sebenarnya, kita mungkin harus membulatkan semua ukuran hingga kelipatan 16, tapi ...) Sebut saja 57 + 5 = 62 byte, kira-kira.

Trie membutuhkan 29 (termasuk terminator nol, '\ 0') untuk penyimpanan, plus ukuran dari node trie, yang merupakan referensi ke array dan daftar node trie anak.

Untuk contoh ini, yang mungkin keluar hampir sama; untuk ribuan, itu mungkin keluar kurang selama Anda memiliki awalan umum.

Sekarang, ketika menggunakan trie dalam kode lain, Anda harus mengonversi ke String, mungkin menggunakan StringBuffer sebagai perantara. Jika banyak string digunakan sekaligus sebagai String, di luar trie, itu adalah kerugian.

Tetapi jika Anda hanya menggunakan sedikit pada saat itu - katakanlah, untuk mencari sesuatu dalam kamus - ketiganya dapat menghemat banyak ruang. Jelas lebih sedikit ruang daripada menyimpannya di HashSet.

Anda mengatakan Anda mengaksesnya "secara serial" - jika itu berarti secara alfabet berurutan, ketiganya juga memberi Anda urutan abjad secara gratis, jika Anda mengulanginya secara mendalam-pertama.

tpdi
sumber
1
apakah trie seperti perpustakaan atau bagaimana cara membuatnya?
euphoria83
Trie akan berguna hanya jika string tokenized, tidak jika seseorang menyimpan teks yang sedang berjalan sebagai string.
MN
5

MEMPERBARUI:

Seperti yang dicatat oleh Mark, tidak ada perbedaan yang signifikan setelah pemanasan JVM (beberapa tes lulus). Diperiksa dengan array yang dibuat ulang atau bahkan pass baru dimulai dengan baris matriks baru. Dengan kemungkinan besar tanda-tanda ini array sederhana dengan akses indeks tidak dapat digunakan untuk mendukung koleksi.

Masih 1-2 pertama melewati array sederhana adalah 2-3 kali lebih cepat.

POST ASLI:

Terlalu banyak kata untuk subjek terlalu mudah untuk diperiksa. Tanpa susunan pertanyaan apa pun beberapa kali lebih cepat daripada wadah kelas mana pun . Saya menjalankan pertanyaan ini mencari alternatif untuk bagian kritis kinerja saya. Ini adalah kode prototipe yang saya buat untuk memeriksa situasi sebenarnya:

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

Dan inilah jawabannya:

Berdasarkan larik (baris 16 aktif):

Time: 7064

Berdasarkan daftar (baris 17 aktif):

Time: 20950

Ada lagi komentar tentang 'lebih cepat'? Ini cukup dimengerti. Pertanyaannya adalah kapan 3 kali lebih cepat lebih baik untuk Anda daripada fleksibilitas Daftar. Tapi ini pertanyaan lain. By the way saya memeriksa ini juga berdasarkan dibangun secara manual ArrayList. Hasilnya hampir sama.

Roman Nikitchenko
sumber
2
3kali lebih cepat benar, tetapi tidak signifikan. 14mstidak lama
0x6C38
1
Benchmark tidak mempertimbangkan pemanasan JVM. Ubah main () menjadi test () dan panggil test dari main berulang kali. Dengan menjalankan tes 3 atau 4, itu berjalan berkali-kali lebih cepat. Pada titik itu, saya melihat array sekitar 9 kali lebih cepat dari array.
Mike
5

Karena sudah ada banyak jawaban bagus di sini, saya ingin memberi Anda beberapa informasi lain tentang tampilan praktis, yaitu perbandingan kinerja penyisipan dan iterasi: array primitif vs Linked-list di Jawa.

Ini adalah pemeriksaan kinerja sederhana yang sebenarnya.
Jadi, hasilnya akan tergantung pada kinerja alat berat.

Kode sumber yang digunakan untuk ini adalah di bawah ini:

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

Hasil Kinerja di bawah ini:

masukkan deskripsi gambar di sini

boraseoksoon
sumber
4

daftar lebih lambat dari array. Jika Anda perlu efisiensi gunakan array. Jika Anda perlu daftar fleksibilitas gunakan.

pejuang
sumber
4

Ingat bahwa ArrayList mengenkapsulasi sebuah array, jadi ada sedikit perbedaan dibandingkan dengan menggunakan array primitif (kecuali kenyataan bahwa suatu Daftar jauh lebih mudah untuk bekerja dengan di java).

Cukup banyak satu-satunya waktu yang masuk akal untuk memilih array ke ArrayList adalah ketika Anda menyimpan primitif, yaitu byte, int, dll dan Anda memerlukan efisiensi ruang tertentu yang Anda dapatkan dengan menggunakan array primitif.

Nuoji
sumber
4

Pilihan array vs List tidak begitu penting (mempertimbangkan kinerja) dalam hal menyimpan objek string. Karena array dan daftar akan menyimpan referensi objek string, bukan objek yang sebenarnya.

  1. Jika jumlah string hampir konstan maka gunakan array (atau ArrayList). Tetapi jika jumlahnya terlalu banyak, lebih baik Anda menggunakan LinkedList.
  2. Jika ada (atau akan) kebutuhan untuk menambah atau menghapus elemen di tengah, maka Anda tentu harus menggunakan LinkedList.
Emre
sumber
4

Saya datang ke sini untuk mendapatkan perasaan yang lebih baik untuk dampak kinerja menggunakan daftar di atas array. Saya harus mengadaptasi kode di sini untuk skenario saya: array / daftar ~ 1000 int menggunakan sebagian besar getter, artinya array [j] vs list.get (j)

Mengambil yang terbaik dari 7 menjadi tidak ilmiah tentang hal itu (beberapa pertama dengan daftar di mana 2,5x lebih lambat) saya mendapatkan ini:

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

- jadi, kira-kira 30% lebih cepat dengan array

Alasan kedua untuk memposting sekarang adalah tidak ada yang menyebutkan dampaknya jika Anda melakukan kode matematika / matriks / simulasi / optimisasi dengan loop bersarang .

Katakanlah Anda memiliki tiga level bersarang dan loop dalam dua kali lebih lambat dibandingkan 8 kali performa Anda tekan. Sesuatu yang akan berjalan dalam sehari sekarang membutuhkan waktu seminggu.

* EDIT Cukup terkejut di sini, karena tendangan saya mencoba mendeklarasikan int [1000] daripada Integer [1000]

array int[] best 299ms iterator
array int[] best 296ms getter

Menggunakan Integer [] vs int [] merupakan hit kinerja ganda, ListArray dengan iterator 3x lebih lambat dari int []. Benar-benar berpikir implementasi daftar Java mirip dengan array asli ...

Kode untuk referensi (panggilan beberapa kali):

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
Xult
sumber
3

Jika Anda tahu sebelumnya seberapa besar data maka sebuah array akan lebih cepat.

Daftar lebih fleksibel. Anda bisa menggunakan ArrayList yang didukung oleh array.

TofuBeer
sumber
ArrayList memiliki metode sureCapacity () yang mengalokasikan array dukungan ke ukuran yang ditentukan.
JesperE
Atau Anda dapat menentukan ukuran pada waktu konstruksi. Juga "lebih cepat" di sini berarti "beberapa mikrodetik untuk mengalokasikan dua area memori bukannya satu"
Aaron Digulla
3

Jika Anda dapat hidup dengan ukuran tetap, array akan lebih cepat dan membutuhkan lebih sedikit memori.

Jika Anda memerlukan fleksibilitas antarmuka Daftar dengan menambahkan dan menghapus elemen, pertanyaannya tetap implementasi mana yang harus Anda pilih. Seringkali ArrayList direkomendasikan dan digunakan untuk kasus apa pun, tetapi juga ArrayList memiliki masalah kinerja jika elemen di awal atau di tengah daftar harus dihapus atau dimasukkan.

Karenanya, Anda mungkin ingin melihat http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list yang memperkenalkan GapList. Implementasi daftar baru ini menggabungkan kekuatan ArrayList dan LinkedList yang menghasilkan kinerja yang sangat baik untuk hampir semua operasi.

Thomas Mauch
sumber
2

Tergantung implementasinya. mungkin saja array tipe primitif akan lebih kecil dan lebih efisien daripada ArrayList. Ini karena array akan menyimpan nilai secara langsung dalam blok memori yang berdekatan, sementara implementasi ArrayList yang paling sederhana akan menyimpan pointer ke setiap nilai. Pada platform 64-bit khususnya, ini dapat membuat perbedaan besar.

Tentu saja, implementasi jvm dimungkinkan untuk memiliki kasus khusus untuk situasi ini, dalam hal ini kinerjanya akan sama.

JRalph
sumber
2

Daftar adalah cara yang disukai di java 1.5 dan seterusnya karena dapat menggunakan obat generik. Array tidak boleh memiliki generik. Array juga memiliki panjang yang ditentukan sebelumnya, yang tidak dapat tumbuh secara dinamis. Menginisialisasi array dengan ukuran besar bukanlah ide yang baik. ArrayList adalah cara untuk mendeklarasikan array dengan generik dan secara dinamis dapat tumbuh. Tetapi jika delete dan insert digunakan lebih sering, maka daftar tertaut adalah struktur data tercepat untuk digunakan.

Shehan Simen
sumber
2

Array yang direkomendasikan di mana-mana Anda dapat menggunakannya alih-alih daftar, terutama jika Anda tahu jumlah item dan ukuran tidak akan berubah.

Lihat praktik terbaik Oracle Java: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

Tentu saja, jika Anda perlu menambah dan menghapus objek dari koleksi berkali-kali daftar yang mudah digunakan.

Nik
sumber
Dokumentasi yang Anda tautkan lebih dari 10 tahun, yaitu berlaku untuk java 1.3. Peningkatan kinerja besar telah dilakukan sejak saat itu ...
assylias
@assylias melihat jawaban di atas, mereka berisi tes kinerja, yang mengatakan bahwa array lebih cepat
Nik
3
Saya tahu saya menulis salah satunya. Tetapi saya tidak berpikir bahwa " array direkomendasikan di mana-mana Anda dapat menggunakannya daripada daftar " adalah saran yang bagus. ArrayList harus menjadi pilihan default di sebagian besar situasi kecuali Anda berurusan dengan primitif dan kode Anda peka terhadap kinerja.
assylias
2

Tidak ada jawaban yang memiliki informasi yang saya minati - pemindaian berulang terhadap array yang sama berkali-kali. Harus membuat tes JMH untuk ini.

Hasil (Java 1.8.0_66 x32, iterating array biasa setidaknya 5 kali lebih cepat dari ArrayList):

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

Uji

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}
Xtra Coder
sumber
2

"Ribuan" bukan jumlah yang besar. Beberapa ribu string panjang paragraf berada di urutan beberapa megabyte dalam ukuran. Jika yang ingin Anda lakukan adalah mengaksesnya secara serial, gunakan Daftar yang terhubung sendiri dan tidak dapat diubah .

Apocalisp
sumber
8 byte pada sebagian besar implementasi 64-bit.
Tom Hawtin - tackline
Apakah ada bukti bahwa hal ini lebih cepat daripada java.util.LinkedList? Yang juga 'di-memori'? Itu juga dapat dibuat tidak berubah, seolah-olah itu membuat perbedaan.
Marquis of Lorne
1

Jangan terjebak dalam optimisasi tanpa pembandingan yang tepat. Seperti orang lain menyarankan menggunakan profiler sebelum membuat asumsi apa pun.

Struktur data yang berbeda yang telah Anda sebutkan memiliki tujuan yang berbeda. Daftar sangat efisien dalam menyisipkan elemen di awal dan di akhir tetapi banyak menderita ketika mengakses elemen acak. Array memiliki penyimpanan tetap tetapi memberikan akses acak cepat. Akhirnya, ArrayList meningkatkan antarmuka ke array dengan membiarkannya tumbuh. Biasanya struktur data yang akan digunakan harus ditentukan oleh bagaimana data yang disimpan akan diakses atau ditambahkan.

Tentang konsumsi memori. Anda sepertinya mencampurkan beberapa hal. Array hanya akan memberi Anda sepotong memori yang berkelanjutan untuk jenis data yang Anda miliki. Jangan lupa bahwa java memiliki tipe data tetap: boolean, char, int, long, float dan Object (ini termasuk semua objek, bahkan array adalah Object). Ini berarti bahwa jika Anda mendeklarasikan array string String [1000] atau MyObject myObjects [1000] Anda hanya mendapatkan 1000 kotak memori yang cukup besar untuk menyimpan lokasi (referensi atau petunjuk) objek. Anda tidak mendapatkan 1000 kotak memori yang cukup besar agar sesuai dengan ukuran objek. Jangan lupa bahwa objek Anda pertama kali dibuat dengan "baru". Ini adalah ketika alokasi memori dilakukan dan kemudian referensi (alamat memori mereka) disimpan dalam array. Objek tidak disalin ke dalam array hanya referensi itu.

potil
sumber
1

Saya tidak berpikir itu membuat perbedaan nyata untuk Strings. Apa yang bersebelahan dalam array string adalah referensi ke string, string itu sendiri disimpan di tempat acak dalam memori.

Array vs. Lists dapat membuat perbedaan untuk tipe primitif, bukan untuk objek. JIKA Anda tahu sebelumnya jumlah elemen, dan tidak perlu fleksibilitas, sebuah array jutaan bilangan bulat atau ganda akan lebih efisien dalam memori dan sedikit dalam kecepatan daripada daftar, karena memang mereka akan disimpan secara berdekatan dan diakses secara instan. Itu sebabnya Java masih menggunakan array chars untuk string, array ints untuk data gambar, dll.

PhiLho
sumber
1

Array lebih cepat - semua memori sudah dialokasikan sebelumnya.

Yakov Fain
sumber
1

Banyak microbenchmark yang diberikan di sini telah menemukan beberapa nanodetik untuk hal-hal seperti array / ArrayList dibaca. Ini cukup masuk akal jika semuanya ada di cache L1 Anda.

Cache tingkat yang lebih tinggi atau akses memori utama dapat memiliki urutan kali besarnya seperti 10nS-100nS, vs lebih seperti 1nS untuk L1 cache. Mengakses ArrayList memiliki tipuan memori ekstra, dan dalam aplikasi nyata Anda dapat membayar biaya ini mulai dari hampir tidak pernah sampai setiap waktu, tergantung pada apa yang kode Anda lakukan di antara akses. Dan, tentu saja, jika Anda memiliki banyak ArrayLists kecil, ini dapat menambah penggunaan memori Anda dan membuatnya lebih mungkin Anda akan kehilangan cache.

Poster asli tampaknya hanya menggunakan satu dan mengakses banyak konten dalam waktu singkat, jadi seharusnya tidak ada kesulitan besar. Tetapi mungkin berbeda untuk orang lain, dan Anda harus berhati-hati ketika menafsirkan microbenchmark.

Namun, Java Strings sangat boros, terutama jika Anda menyimpan banyak yang kecil (lihat saja dengan penganalisa memori, tampaknya> 60 byte untuk string beberapa karakter). Array string memiliki tipuan ke objek String, dan lainnya dari objek String ke char [] yang berisi string itu sendiri. Jika ada sesuatu yang akan meledakkan cache L1 Anda, ini adalah ini, dikombinasikan dengan ribuan atau puluhan ribu String. Jadi, jika Anda serius - benar-benar serius - tentang membuang sebanyak mungkin kinerja maka Anda dapat melihat melakukannya secara berbeda. Anda bisa, katakanlah, memegang dua array, char [] dengan semua string di dalamnya, satu demi satu, dan int [] dengan offset ke awal. Ini akan menjadi PITA untuk melakukan apa saja, dan Anda hampir pasti tidak membutuhkannya. Dan jika Anda melakukannya, Anda

Alex Hayward
sumber
0

Itu tergantung pada bagaimana Anda harus mengaksesnya.

Setelah menyimpan, jika Anda terutama ingin melakukan operasi pencarian, dengan sedikit atau tanpa memasukkan / menghapus, kemudian pergi untuk Array (karena pencarian dilakukan dalam O (1) dalam array, sedangkan add / delete mungkin perlu memesan ulang elemen) .

Setelah menyimpan, jika tujuan utama Anda adalah untuk menambah / menghapus string, dengan sedikit atau tanpa operasi pencarian, kemudian pergi untuk Daftar.

Vikram
sumber
0

ArrayList secara internal menggunakan objek array untuk menambah (atau menyimpan) elemen. Dengan kata lain, ArrayList didukung oleh data array -struktur. ArrayList diubah ukurannya (atau dinamis).

Array lebih cepat daripada Array karena ArrayList secara internal menggunakan array. jika kita dapat langsung menambahkan elemen dalam Array dan secara tidak langsung menambahkan elemen dalam Array melalui ArrayList selalu secara langsung mekanisme lebih cepat daripada mekanisme tidak langsung.

Ada dua metode add () overload di kelas ArrayList:
1 add(Object) .: menambahkan objek ke akhir daftar.
2 add(int index , Object ) .: menyisipkan objek yang ditentukan pada posisi yang ditentukan dalam daftar.

Bagaimana ukuran ArrayList tumbuh secara dinamis?

public boolean add(E e)        
{       
     ensureCapacity(size+1);
     elementData[size++] = e;         
     return true;
}

Poin penting yang perlu diperhatikan dari kode di atas adalah bahwa kami memeriksa kapasitas ArrayList, sebelum menambahkan elemen. sureCapacity () menentukan berapa ukuran elemen terisi saat ini dan berapa ukuran maksimum array. Jika ukuran elemen yang diisi (termasuk elemen baru yang akan ditambahkan ke kelas ArrayList) lebih besar dari ukuran maksimum array, maka tambah ukuran array. Tetapi ukuran array tidak dapat ditingkatkan secara dinamis. Jadi yang terjadi secara internal adalah array baru yang dibuat dengan kapasitas

Sampai Jawa 6

int newCapacity = (oldCapacity * 3)/2 + 1;

(Pembaruan) Dari Java 7

 int newCapacity = oldCapacity + (oldCapacity >> 1);

juga, data dari array lama disalin ke dalam array baru.

Memiliki metode overhead di ArrayList itu sebabnya Array lebih cepat daripada ArrayList.

Vipin Jain
sumber
0

Array - Akan selalu lebih baik ketika kita harus mencapai pengambilan hasil yang lebih cepat

Daftar - Melakukan hasil pada penyisipan dan penghapusan karena mereka dapat dilakukan di O (1) dan ini juga menyediakan metode untuk menambah, mengambil dan menghapus data dengan mudah. Jauh lebih mudah digunakan.

Tapi selalu ingat bahwa pengambilan data akan cepat ketika posisi indeks dalam array tempat data disimpan - diketahui.

Ini bisa dicapai dengan baik dengan menyortir array. Karenanya ini meningkatkan waktu untuk mengambil data (yaitu; menyimpan data + menyortir data + mencari posisi di mana data ditemukan). Karenanya ini meningkatkan latensi tambahan untuk mengambil data dari array bahkan mereka mungkin pandai mengambil data lebih cepat.

Oleh karena itu ini dapat diselesaikan dengan struktur data trie atau struktur data terner. Seperti dibahas di atas, struktur data trie akan sangat efisien dalam mencari data, pencarian untuk kata tertentu dapat dilakukan dalam O (1) besarnya. Ketika waktu berarti; jika Anda harus mencari dan mengambil data dengan cepat, Anda dapat pergi dengan struktur data trie.

Jika Anda ingin ruang memori Anda dikonsumsi lebih sedikit dan Anda ingin memiliki kinerja yang lebih baik maka gunakan struktur data ternary. Keduanya cocok untuk menyimpan sejumlah besar string (misalnya; seperti kata-kata yang terkandung dalam kamus).

Rajasuba Subramanian
sumber