Java 8'de, ArrayList'in varsayılan kapasitesi neden şimdi sıfır?


94

Hatırladığım kadarıyla, Java 8'den önce varsayılan kapasite ArrayList10'du.

Şaşırtıcı bir şekilde, varsayılan (void) kurucu hakkındaki yorum hala şunu söylüyor: Constructs an empty list with an initial capacity of ten.

Kimden 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;
}

Yanıtlar:


107

Teknik olarak, 10destek dizisinin tembel bir şekilde başlatılmasını kabul ederseniz, sıfır değil. Görmek:

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);
}

nerede

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

Bahsettiğiniz şey, başlangıçta boş olan tüm ArrayListnesneler arasında paylaşılan sıfır boyutlu ilk dizi nesnesidir . Java 7'de de mevcut olan bir optimizasyonun kapasitesi tembel10 olarak garanti edilmektedir .

Kuşkusuz, müteahhit sözleşmesi tamamen doğru değil. Belki de buradaki kafa karışıklığının kaynağı budur.

Arka fon

İşte Mike Duigou'dan bir E-posta

Boş ArrayList ve HashMap yamasının güncellenmiş bir sürümünü yayınladım.

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

Bu gözden geçirilmiş uygulama , her iki sınıfa da yeni alan getirmez. ArrayList için, destek dizisinin geç tahsisi yalnızca liste varsayılan boyutta oluşturulmuşsa gerçekleşir. Performans analizi ekibimize göre, ArrayList örneklerinin yaklaşık% 85'i varsayılan boyutta oluşturulmaktadır, bu nedenle bu optimizasyon vakaların büyük çoğunluğu için geçerli olacaktır.

HashMap için, paket dizisi gerekene kadar istenen başlangıç ​​boyutunu izlemek için eşik alanından reklam öğesi kullanılır. Okuma tarafında, boş harita durumu isEmpty () ile test edilir. Yazma boyutunda, kova dizisini şişirme ihtiyacını saptamak için (tablo == EMPTY_TABLE) karşılaştırması kullanılır. ReadObject'te verimli bir başlangıç ​​kapasitesi seçmeye çalışmak için biraz daha çalışma var.

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


4
Bugs.java.com/bugdatabase/view_bug.do?bug_id=7143928'e göre, yığın kullanımını azaltmaya ve yanıt sürelerini iyileştirmeye yol açar (iki uygulama için sayılar gösterilmiştir)
Thomas Kläger

3
@khelwood: ArrayList, bu Javadoc dışında kapasitesini gerçekten "rapor etmiyor": hiçbir getCapacity()yöntem veya benzeri bir şey yok . (Bununla birlikte, ensureCapacity(7)varsayılan olarak başlatılmış bir ArrayList için
işlemsiz

11
İyi kazdı. Varsayılan başlangıç ​​kapasitesi aslında sıfır değil 10'dur ve varsayılan durum özel bir durum olarak tembel olarak tahsis edilmiştir. Bunu, ArrayListarginsiz yapıcıyla oluşturulmuş bir öğeye tekrar tekrar ekleyip oluşturucuya sıfırı iletirseniz intve dahili dizi boyutuna yansıtıcı olarak veya bir hata ayıklayıcıda bakarsanız bunu gözlemleyebilirsiniz . Varsayılan durumda, dizi 1,5x büyüme oranının ardından 0'dan 10'a, ardından 15, 22'ye atlar. İlk kapasite olarak sıfırın geçilmesi, 0'dan 1, 2, 3, 4, 6, 9, 13, 19 .... 'e büyüme ile sonuçlanır.
Stuart,

14
Ben Mike Duigou, değişikliğin ve alıntılanan e-postanın yazarı ve bu mesajı onaylıyorum. 🙂 Stuart'ın dediği gibi, motivasyon performanstan çok yer tasarrufu sağlamaktı, ancak arka plan dizisinin oluşturulmasından sık sık kaçınılması nedeniyle küçük bir performans avantajı da var.
Mike Duigou

4
@assylias:; ^) hayır, bir singleton emptyList()birkaç boş ArrayListörnekten daha az bellek tükettiği için hala yerini koruyor . Şu anda daha az önemlidir ve bu nedenle her yerde, özellikle daha sonra öğe ekleme olasılığı daha yüksek olan yerlerde gerekli değildir. Ayrıca, bazen değişmez bir boş liste istediğinizi ve ardından emptyList()gitmenin yolunun bu olduğunu unutmayın.
Holger

24

Java 8'de, ArrayList nesnesine en az bir nesne ekleyene kadar ArrayList'in varsayılan kapasitesi 0'dır (buna lazy initialization diyebilirsiniz).

Şimdi soru, bu değişikliğin neden JAVA 8'de yapıldığı?

Cevap, hafıza tüketiminden tasarruf etmektir. Gerçek zamanlı java uygulamalarında milyonlarca dizi listesi nesnesi oluşturulur. 10 nesnenin varsayılan boyutu, oluşturma sırasında temeldeki dizi için 10 işaretçi (40 veya 80 bayt) ayırdığımız ve bunları boş değerlerle doldurduğumuz anlamına gelir. Boş bir dizi (boş değerlerle dolu) çok fazla bellek kullanır.

Tembel başlatma, bu bellek tüketimini gerçekten dizi listesini kullanacağınız ana kadar erteler.

Lütfen yardım için aşağıdaki koda bakın.

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

Makale Java 8'de ArrayList'in varsayılan kapasitesi bunu ayrıntılı olarak açıklamaktadır.


7

Bir ArrayList ile yapılan ilk işlem, addAllondan fazla elemanı olan bir koleksiyonu iletmekse , o zaman ArrayList'in içeriğini tutmak için ilk on elemanlı bir dizi yaratmaya yönelik herhangi bir çaba pencereden dışarı atılır. Bir ArrayList'e bir şey eklendiğinde, ortaya çıkan listenin boyutunun arka plan deposunun boyutunu aşıp aşmayacağını test etmek gerekir; İlk yedekleme deposunun boyutunun on yerine sıfır olmasına izin verilmesi, bu testin ilk işlemi "ekleme" olan ve ilk on öğeli dizinin oluşturulmasını gerektiren bir listenin ömrü boyunca fazladan bir kez başarısız olmasına neden olur, ancak bu maliyet hiç kullanılmayan on maddelik bir dizi yaratmanın maliyetinden daha az.

Bununla birlikte, mevcut olandan sonra listeye kaç öğenin (varsa) ekleneceğini belirten bir "addAll" aşırı yüklenmesi olsaydı, bazı bağlamlarda performansı daha da artırmak mümkün olabilirdi ve hangileri olabilirdi? bunu tahsis davranışını etkilemek için kullanır. Bazı durumlarda, listeye son birkaç öğeyi ekleyen kod, listenin hiçbir zaman bunun ötesinde bir alana ihtiyaç duymayacağı konusunda oldukça iyi bir fikre sahip olacaktır. Bir listenin bir kez doldurulacağı ve bundan sonra hiçbir zaman değiştirilmeyeceği birçok durum vardır. Nokta kodunda bir listenin nihai boyutunun 170 öğe olacağını biliyorsa, 150 öğesi ve 160 boyutunda bir destek deposu vardır,


Hakkında çok iyi noktalar addAll(). Bu, ilk malloc çevresinde verimliliği artırmak için bir başka fırsattır.
kevinarpe

@kevinarpe: Keşke Java'nın kitaplığı, programların nesnelerin nasıl kullanılabileceğini göstermesi için daha fazla şekilde tasarlanmış olsaydı. Örneğin eski alt dize stili, bazı kullanım durumları için kötüyken, diğerleri için mükemmeldi. "Orijinalden daha uzun süre dayanması muhtemel olan alt dize" ve "orijinalinden daha uzun süre dayanması olası olmayan alt dize" için ayrı işlevler olsaydı ve kod% 90 oranında doğru olanı kullansaydı, bunların her ikisinden de çok daha iyi performans göstereceğini düşünürdüm eski veya yeni dize uygulaması.
supercat

3

Soru 'neden?'

Bellek profili oluşturma incelemeleri (örneğin ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ), boş (boşlarla dolu) dizilerin tonlarca bellek kapladığını gösterir.

10 nesnenin varsayılan boyutu, oluşturma sırasında temeldeki dizi için 10 işaretçi (40 veya 80 bayt) ayırdığımız ve bunları boş değerlerle doldurduğumuz anlamına gelir. Gerçek java uygulamaları milyonlarca dizi listesi oluşturur.

Yapılan değişiklik, ^ W'yi kaldırır ve bu bellek tüketimini, gerçekten dizi listesini kullanacağınız ana kadar ertele.


Lütfen "tüket" i "atık" olarak düzeltin. Sağladığınız bağlantı, hafızayı her yerde yutmaya başladıkları anlamına gelmez, sadece boş elemanlara sahip dizilerin orantısız bir şekilde onlar için ayrılan belleği boşa harcadığı anlamına gelir. "Tüketim", bellekleri tahsislerinin ötesinde sihirli bir şekilde kullandıkları anlamına gelir, ki durum böyle değildir.
mechalynx

2

Yukarıdaki sorudan sonra Java 8'in ArrayList Dokümanını inceledim. Varsayılan boyutun hala yalnızca 10 olduğunu buldum.

Lütfen aşağıya bakın


0

JAVA 8'de ArrayList varsayılan boyutu hala 10'dur. JAVA 8'de yapılan tek değişiklik, bir kodlayıcı 10'dan az eleman eklerse, kalan dizi listesi boş yerlerinin boş olarak belirtilmemesidir. Bunu söylemek, kendimi bu durumdan ve tutulmadan geçtiğim için JAVA 8'in bu değişikliğine bakmamı sağladı.

Aşağıdaki ekran görüntüsüne bakarak bu değişikliği gerekçelendirebilirsiniz. İçinde ArrayList boyutunun Object [10] 'da 10 olarak belirtildiğini ancak görüntülenen elemanların sayısının sadece 7 olduğunu görebilirsiniz. Rest null değerli elemanlar burada görüntülenmez. JAVA 7'de aşağıdaki ekran görüntüsü tek bir değişiklikle aynıdır; bu, JAVA 8'de bu yük kaldırılırken, kodlayıcının tam dizi listesini yineliyorsa, boş değerlerin işlenmesi için kod yazması gereken boş değer öğelerinin de görüntülendiği tek bir değişiklikle aynıdır. kodlayıcı / geliştirici başkanı.

Ekran görüntüsü bağlantısı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.