Di Java 8, mengapa kapasitas default ArrayList sekarang nol?

93

Seingat saya, sebelum Java 8, kapasitas defaultnya ArrayListadalah 10.

Anehnya, komentar pada konstruktor default (void) masih mengatakan: Constructs an empty list with an initial capacity of ten.

Dari ArrayList.java:

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
kevinarpe
sumber

Jawaban:

105

Secara teknis, ini 10, bukan nol, jika Anda mengakui inisialisasi larik dukungan yang malas. Lihat:

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

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

dimana

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

Yang Anda maksud hanyalah objek larik awal berukuran nol yang dibagikan di antara semua objek yang awalnya kosong ArrayList. Yaitu kapasitas 10dijamin malas , pengoptimalan yang ada juga di Java 7.

Diakui, kontrak konstruktor tidak sepenuhnya akurat. Mungkin inilah yang menjadi sumber kebingungan di sini.

Latar Belakang

Ini E-Mail oleh Mike Duigou

Saya telah memposting versi terbaru dari ArrayList dan patch HashMap kosong.

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/webrev/

Implementasi yang direvisi ini tidak memperkenalkan bidang baru ke kedua kelas. Untuk ArrayList, alokasi malas dari backing array hanya terjadi jika daftar dibuat pada ukuran default. Menurut tim analisis kinerja kami, sekitar 85% instans ArrayList dibuat dalam ukuran default sehingga pengoptimalan ini akan valid untuk sebagian besar kasus.

Untuk HashMap, penggunaan materi iklan dibuat dari bidang ambang batas untuk melacak ukuran awal yang diminta hingga susunan keranjang diperlukan. Di sisi baca, kasus peta kosong diuji dengan isEmpty (). Pada ukuran tulis, perbandingan (table == EMPTY_TABLE) digunakan untuk mendeteksi kebutuhan untuk memekarkan larik keranjang. Di readObject ada sedikit pekerjaan lagi untuk mencoba memilih kapasitas awal yang efisien.

Dari: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html

Lukas Eder
sumber
4
Menurut bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928 ini mengarah pada pengurangan penggunaan heap dan waktu respons yang lebih baik (angka untuk dua aplikasi ditampilkan)
Thomas Kläger
3
@khelwood: ArrayList tidak benar-benar "melaporkan" kapasitasnya, selain melalui Javadoc ini: tidak ada getCapacity()metode, atau semacamnya. (Yang mengatakan, sesuatu seperti ensureCapacity(7)adalah tidak ada operasi untuk ArrayList yang diinisialisasi default, jadi saya kira kita benar-benar harus bertindak seolah-olah kapasitas awalnya benar-benar 10 ...)
ruakh
10
Penggalian yang bagus. Kapasitas awal default memang bukan nol, tetapi 10, dengan kasus default dialokasikan secara malas sebagai kasus khusus. Anda dapat mengamati ini jika Anda berulang kali menambahkan elemen ke yang ArrayListdibuat dengan konstruktor no-arg vs meneruskan nol ke intkonstruktor, dan jika Anda melihat ukuran larik internal secara reflektif atau dalam debugger. Dalam kasus default, larik melompat dari panjang 0 ke 10, lalu ke 15, 22, mengikuti tingkat pertumbuhan 1,5x. Melewati nol sebagai hasil kapasitas awal dalam pertumbuhan dari 0 ke 1, 2, 3, 4, 6, 9, 13, 19 ....
Stuart Marks
13
Saya Mike Duigou, penulis perubahan dan kutipan email dan saya menyetujui pesan ini. 🙂 Seperti yang dikatakan Stuart, motivasi utamanya adalah tentang penghematan ruang daripada kinerja meskipun ada juga sedikit keuntungan kinerja karena sering menghindari pembuatan susunan pendukung.
Mike Duigou
4
@assylias:; ^) tidak, itu masih memiliki tempatnya sebagai singleton yang emptyList()masih mengkonsumsi lebih sedikit memori daripada beberapa ArrayListinstance kosong . Ini hanya kurang penting sekarang dan karenanya tidak diperlukan di setiap tempat, terutama tidak di tempat dengan kemungkinan lebih tinggi untuk menambahkan elemen di lain waktu. Juga perlu diingat bahwa Anda terkadang menginginkan daftar kosong yang tidak dapat diubah dan kemudian emptyList()adalah cara untuk pergi.
Holger
24

Di java 8, kapasitas default ArrayList adalah 0 hingga kami menambahkan setidaknya satu objek ke objek ArrayList (Anda dapat menyebutnya inisialisasi malas).

Sekarang pertanyaannya adalah mengapa perubahan ini dilakukan di JAVA 8?

Jawabannya adalah untuk menghemat konsumsi memori. Jutaan objek daftar larik dibuat dalam aplikasi java waktu nyata. Ukuran default 10 objek berarti kita mengalokasikan 10 pointer (40 atau 80 byte) untuk array yang mendasari saat pembuatan dan mengisinya dengan null. Array kosong (diisi dengan nulls) menempati banyak memori.

Inisialisasi malas menunda konsumsi memori ini sampai Anda benar-benar akan menggunakan daftar array.

Silakan lihat kode di bawah ini untuk bantuan.

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

Artikel Kapasitas default ArrayList di Java 8 menjelaskannya secara detail.

Shailesh Vikram Singh
sumber
7

Jika operasi pertama yang dilakukan dengan ArrayList adalah meneruskan addAllkoleksi yang memiliki lebih dari sepuluh elemen, maka segala upaya yang dilakukan untuk membuat larik sepuluh elemen awal untuk menampung konten ArrayList akan dibuang ke luar jendela. Kapan pun sesuatu ditambahkan ke ArrayList, perlu untuk menguji apakah ukuran daftar yang dihasilkan akan melebihi ukuran penyimpanan dukungan; mengizinkan penyimpanan cadangan awal untuk memiliki ukuran nol daripada sepuluh akan menyebabkan pengujian ini gagal satu kali ekstra selama masa aktif daftar yang operasi pertamanya adalah "tambah" yang akan memerlukan pembuatan larik sepuluh item awal, tetapi biayanya adalah kurang dari biaya pembuatan larik sepuluh item yang tidak akan pernah digunakan.

Seperti yang telah dikatakan, mungkin saja untuk meningkatkan kinerja lebih jauh dalam beberapa konteks jika ada kelebihan "addAll" yang menentukan berapa banyak item (jika ada) yang kemungkinan akan ditambahkan ke daftar setelah yang ada, dan mana yang dapat gunakan itu untuk mempengaruhi perilaku alokasinya. Dalam beberapa kasus, kode yang menambahkan beberapa item terakhir ke daftar akan memiliki gagasan yang cukup bagus bahwa daftar tidak akan pernah membutuhkan spasi lebih dari itu. Ada banyak situasi di mana daftar akan diisi satu kali dan tidak pernah diubah setelah itu. Jika pada kode poin mengetahui bahwa ukuran akhir dari sebuah daftar adalah 170 elemen, ia memiliki 150 elemen dan penyimpanan pendukung berukuran 160,

supercat
sumber
Poin yang sangat bagus tentang addAll(). Itu adalah kesempatan lain untuk meningkatkan efisiensi di sekitar malloc pertama.
kevinarpe
@kevinarpe: Saya berharap perpustakaan Java telah merekayasa lebih banyak cara agar program dapat menunjukkan bagaimana sesuatu kemungkinan besar akan digunakan. Gaya substring yang lama, misalnya, buruk untuk beberapa kasus penggunaan, tetapi sangat baik untuk yang lain. Seandainya ada fungsi terpisah untuk "substring yang kemungkinan bertahan lebih lama dari aslinya" dan "substring yang tidak mungkin bertahan lebih lama dari aslinya", dan kode menggunakan yang benar 90% dari waktu, saya akan berpikir itu bisa sangat mengungguli baik implementasi string lama atau baru.
supercat
3

Pertanyaannya adalah 'mengapa?'.

Pemeriksaan profil memori (misalnya ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) menunjukkan bahwa array kosong (diisi dengan nulls) menempati banyak memori.

Ukuran default 10 objek berarti kita mengalokasikan 10 pointer (40 atau 80 byte) untuk array yang mendasari saat pembuatan dan mengisinya dengan null. Aplikasi java nyata membuat jutaan daftar array.

Modifikasi yang diperkenalkan menghapus ^ W menunda konsumsi memori ini sampai Anda benar-benar akan menggunakan daftar larik.

ya_pulser
sumber
Harap perbaiki "konsumsi" dengan "limbah". Tautan yang Anda berikan tidak menyiratkan bahwa mereka mulai melahap memori di mana-mana, hanya saja array dengan elemen null menyia-nyiakan memori yang dialokasikan untuknya, secara tidak proporsional. "Konsumsi" menyiratkan bahwa mereka secara ajaib menggunakan memori di luar alokasinya, yang sebenarnya tidak terjadi.
mechalynx
1

Setelah pertanyaan di atas saya pergi melalui ArrayList Document of Java 8. Saya menemukan ukuran default masih 10 saja.

Silahkan lihat di bawah ini

Rahul Maurya
sumber
0

Ukuran default ArrayList di JAVA 8 adalah stil 10. Satu-satunya perubahan yang dibuat di JAVA 8 adalah jika pembuat kode menambahkan elemen kurang dari 10 maka tempat kosong daftar array yang tersisa tidak ditentukan ke null. Mengatakan demikian karena saya sendiri telah melalui situasi ini dan gerhana membuat saya melihat perubahan JAVA 8 ini.

Anda dapat membenarkan perubahan ini dengan melihat tangkapan layar di bawah ini. Di dalamnya Anda dapat melihat bahwa ukuran ArrayList ditentukan sebagai 10 di Object [10] tetapi jumlah elemen yang ditampilkan hanya 7. Elemen nilai null lainnya tidak ditampilkan di sini. Di JAVA 7, screenshot di bawah ini sama dengan hanya satu perubahan yaitu elemen nilai null juga ditampilkan di mana pembuat kode perlu menulis kode untuk menangani nilai null jika dia mengulang daftar array lengkap sementara di JAVA 8 beban ini dihapus dari kepala pembuat kode / pengembang.

Tautan tangkapan layar.

TechTeddy
sumber