Neden ilk kapasiteye sahip bir ArrayList başlatmalıyım?


149

Her zamanki kurucusu ArrayList:

ArrayList<?> list = new ArrayList<>();

Ancak başlangıç ​​kapasitesi için bir parametreye sahip aşırı yüklenmiş bir kurucu da var:

ArrayList<?> list = new ArrayList<>(20);

ArrayListİstediğimiz gibi ekleyebildiğimizde, başlangıç ​​kapasitesine sahip bir an oluşturmak neden yararlıdır ?


17
ArrayList kaynak kodunu görmeye çalıştınız mı?
AmitG

@Joachim Sauer: Kaynağı dikkatlice okuduğumuzda bazen bir biliş elde ederiz. Kaynağı okuduysa, deniyordum. Senin bakış açını anladım. Teşekkürler.
AmitG 15:13

ArrayList düşük performans süresi, neden böyle bir yapı kullanmak istersiniz
PositiveGuy

Yanıtlar:


196

Boyutunun ne olacağını önceden biliyorsanız ArrayList, başlangıç ​​kapasitesini belirtmek daha etkilidir. Bunu yapmazsanız, liste büyüdükçe dahili dizinin tekrar tekrar atanması gerekir.

Son liste ne kadar büyük olursa, yeniden tahsisleri önleyerek daha fazla zaman kazanırsınız.

Bununla birlikte, ön ayırma olmadan bile n, bir arkasına eleman yerleştirmenin ArrayListtoplam O(n)zaman alması garanti edilir . Başka bir deyişle, bir öğeyi eklemek, itfa edilmiş sabit zamanlı bir işlemdir. Bu, her yeniden tahsisin dizinin boyutunu katlanarak, tipik olarak bir faktör ile arttırmasıyla elde edilir 1.5. Bu yaklaşımla, toplam işlem sayısının olduğu gösterilebilirO(n) .


5
Önceden tahsis bilinen boyutları iyi bir fikir olsa da, genellikle korkunç değil yapmıyor: Eğer hakkında gerekecektir günlüğüne (n) bir son boyutu ile bir liste için yeniden tahsisleri n çok değil.
Joachim Sauer

2
@PeterOlson çalışma saatleri O(n log n)yapıyor olacaktı . Bu büyük bir fazla tahmin ( üst sınır olması nedeniyle büyük O ile teknik olarak doğru olsa da ). Toplamda s + s * 1.5 + s * 1.5 ^ 2 + ... + s * 1.5 ^ m (s * 1.5 ^ m <n <s * 1.5 ^ (m + 1)) öğelerini kopyalar. Toplamlarda iyi değilim, bu yüzden size kafamın üstünden kesin matematik veremiyorum (faktör 2'yi yeniden boyutlandırmak için, 2n, bu yüzden 1.5n olabilir veya küçük bir sabit alabilir), ama değil t Bu toplamın en fazla n'den daha büyük bir sabit faktör olduğunu görmek için fazla şaşılık almayın. Bu yüzden elbette O (n) olan O (k * n) kopyalarını alır. log nn

1
@delnan: Bununla tartışamazsın! ;) BTW, şaşırarak tartışmanızı gerçekten sevdim; hileler repertuarım için ekleyeceğim.
NPE

6
Arguyu iki katına çıkarmak daha kolaydır. Diyelim ki, bir öğe ile başlayarak dolu olduğunuzda iki katına çıkabilirsiniz. 8 eleman eklemek istediğinizi varsayalım. Birini ekleyin (maliyet: 1). İki - çift takın, bir elemanı kopyalayın ve iki ekleyin (maliyet: 2). Üç - çift takın, iki elemanı kopyalayın, üç yerleştirin (maliyet: 3). Dört ekle (maliyet: 1). Beş - çift yerleştirin, dört elemanı kopyalayın, beşini ekleyin (maliyet: 5). Altı, yedi ve sekiz ekleyin (maliyet: 3). Toplam maliyet: 1 + 2 + 3 + 1 + 5 + 3 = 16; bu, eklenen öğe sayısının iki katıdır . Bu çizimden, ortalama maliyetin kesici uç başına genel olarak iki olduğunu kanıtlayabilirsiniz .
Eric Lippert

9
Zaman içindeki maliyet . Ayrıca boşa harcanan alan miktarının zaman içinde değiştiğini, zamanın% 0'ı ve zamanın% 100'üne yakın olduğunu görebilirsiniz. Faktörün 2'den 1,5 veya 4 veya 100'e değiştirilmesi veya ortalama boşa harcanan alan miktarını ve kopyalama için harcanan ortalama süreyi ne değiştirirse de, faktör ne olursa olsun zaman karmaşıklığı ortalama olarak doğrusal kalır.
Eric Lippert

41

Çünkü ArrayLista, dinamik olarak yeniden boyutlandırma dizi , bir başlangıç (varsayılan), sabit boyutu ile bir dizi olarak uygulanan demektir veri yapısı. Bu dolduğunda, dizi çift boyutlu bir diziye genişletilir. Bu işlem maliyetlidir, bu yüzden mümkün olduğunca azını istersiniz.

Bu nedenle, üst sınırınızın 20 öğe olduğunu biliyorsanız, başlangıç ​​uzunluğu 20 olan diziyi oluşturmak varsayılan olarak, örneğin 15'i kullanmaktan daha iyidir ve sonra yeniden boyutlandırın 15*2 = 30ve genişletme için döngüleri harcarken yalnızca 20'yi kullanın.

Not - AmitG'nin dediği gibi, genişleme faktörü uygulamaya özgüdür (bu durumda (oldCapacity * 3)/2 + 1)


9
aslındaint newCapacity = (oldCapacity * 3)/2 + 1;
AmitG

25

Arraylist'in varsayılan boyutu 10'dur .

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    } 

Eğer 100 veya daha fazla kayıt ekleyecekseniz, bellek yeniden tahsis yükünü görebilirsiniz.

ArrayList<?> list = new ArrayList<>();    
// same as  new ArrayList<>(10);      

Dolayısıyla, Arraylist'te saklanacak elemanların sayısı hakkında herhangi bir fikriniz varsa, 10 ile başlamak ve daha sonra artırmak yerine bu boyutta Arraylist oluşturmak daha iyidir.


Gelecekte varsayılan kapasitenin JDK sürümleri için her zaman 10 olacağının garantisi yoktur -private static final int DEFAULT_CAPACITY = 10
vikingsteve

17

Aslında 2 ay önce konuyla ilgili bir blog yazısı yazdım . Makale C # için List<T>ama Java'nın ArrayListçok benzer bir uygulaması var. Yana ArrayListdinamik bir dizi kullanılarak uygulanır, bu talep üzerine boyutunda artar. Dolayısıyla kapasite oluşturucunun nedeni optimizasyon amaçlıdır.

Bu yeniden boyutlandırma işlemlerinden biri gerçekleştiğinde, ArrayList dizinin içeriğini eskisinin iki katı kapasiteye sahip yeni bir diziye kopyalar. Bu işlem O (n) süresinde çalışır .

Misal

Boyutun nasıl ArrayListartacağına dair bir örnek :

10
16
25
38
58
... 17 resizes ...
198578
297868
446803
670205
1005308

Liste kapasiteli başlar Yani 1011. madde eklenir, bu tarafından artıştır 50% + 1için 16. 17. maddede bu ArrayListoran tekrar artırılır 25. Şimdi, istenen kapasitenin zaten bilindiği bir liste oluşturduğumuz örneği düşünün 1000000. ArrayListBoyut oluşturucu olmadan oluşturma çağrılırArrayList.add 1000000 O (1) 'i normal olarak veya O (n) ' yi yeniden boyutlandırmada alan süreleri .

1000000 + 16 + 25 + ... + 670205 + 1005308 = 4015851 işlem

Yapıcıyı kullanarak ve ardından çağırarak bunu karşılaştırın ArrayList.add O (1) ' de çalışacağı garanti edilen .

1000000 + 1000000 = 2000000 işlem

Java vs C #

Java yukarıdaki gibidir, 10her boyuttan başlayarak ve yeniden boyutlandırılır 50% + 1. C # başlar4 ve her yeniden boyutlandırmada iki katına çıkarak çok daha agresif bir şekilde artar. 1000000C # kullanımları için yukarıdan örnek ekler 3097084işlemleri.

Referanslar


9

Bir ArrayList öğesinin başlangıç ​​boyutunun ayarlanması, örneğin ArrayList<>(100), dahili belleğin yeniden tahsis edilme sayısını azaltır.

Misal:

ArrayList example = new ArrayList<Integer>(3);
example.add(1); // size() == 1
example.add(2); // size() == 2, 
example.add(2); // size() == 3, example has been 'filled'
example.add(3); // size() == 4, example has been 'expanded' so that the fourth element can be added. 

Yukarıdaki örnekte gördüğünüz gibi - ArrayListgerekiyorsa genişletilebilir. Bunun size göstermediği şey, Arraylist'in büyüklüğünün genellikle iki katına çıkmasıdır (ancak yeni boyutun uygulamanıza bağlı olduğunu unutmayın). Oracle'dan alıntılar :

"Her ArrayList örneğinin bir kapasitesi vardır. Kapasite, öğeleri listede saklamak için kullanılan dizinin boyutudur. Her zaman en az liste boyutu kadar büyüktür. Öğeler bir ArrayList'e eklendiğinde kapasitesi otomatik olarak artar. Büyüme politikasının ayrıntıları, bir unsur eklemenin sabit amortisman süresi maliyetine sahip olmasının ötesinde belirtilmemiştir. "

Açıkçası, ne tür bir menzil tutacağınız hakkında hiçbir fikriniz yoksa, boyutu ayarlamak muhtemelen iyi bir fikir olmaz - ancak, belirli bir menziliniz varsa, ilk kapasitenin ayarlanması bellek verimliliğini artıracaktır. .


3

ArrayList birçok değer içerebilir ve büyük ilk eklemeler yaparken ArrayList'e, sonraki öğe için daha fazla yer ayırmaya çalıştığında CPU döngülerini boşa harcamamaya başlamak için daha büyük bir depolama alanı ayırmasını söyleyebilirsiniz. Böylece başlangıçta biraz yer ayırmak daha verimlidir.


3

Bu, her bir nesne için yeniden tahsis için olası çabalardan kaçınmaktır.

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

dahili new Object[]olarak oluşturulur. Arrayliste eleman eklediğinizde JVM'nin
oluşturulması için çaba harcanması new Object[]gerekir. Yeniden tahsis için yukarıdaki kodunuz (düşündüğünüz herhangi bir algo) yoksa, ne zaman çağırırsanız arraylist.add()o new Object[]zaman yaratılmalıdır, ki bu anlamsızdır ve eklenecek her nesne için boyutu 1 arttırmak için zaman kaybediyoruz. Bu nedenle Object[]aşağıdaki formülle boyutunu arttırmak daha iyidir .
(JSL, dinamik olarak büyüyen arraylist için her seferinde 1 oranında büyümek yerine aşağıda verilen tahmin formülünü kullanmıştır.

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

ArrayList olacak değil , tek tek her için yeniden tahsisini gerçekleştirmek add- zaten içten bazı büyüme formülü kullanır. Dolayısıyla soru cevaplanmamıştır.
AH

@AH Cevabım negatif test için . Lütfen satırlar arasında okuyun. Dedim "sen (arraylist.add çağırmak zaman anlamsız ve biz zaman kaybetmeden hangi oluşturulacak olan siz), yeniden belirlenmesi kodu (sizin algo herhangi düşünüyorum) Yukarıdaki ardından yeni Object [] her zaman yok demektir." ve kodu olan int newCapacity = (oldCapacity * 3)/2 + 1;ArrayList sınıfının mevcut olan. Hala cevaplanmadığını mı düşünüyorsun?
AmitG

1
Hala cevaplanmadığını düşünüyorum: ArrayListİtfa edilen yeniden tahsis, her durumda , başlangıç ​​kapasitesi için herhangi bir değerle gerçekleşir. Ve soru şu: Neden ilk kapasite için standart olmayan bir değer kullanıyorsunuz? Bunun yanı sıra: "satırlar arasında okuma" teknik bir cevapta istenen bir şey değildir. ;-)
AH

@AH ArrayList'te yeniden tahsis sürecimiz olmasaydı neler olduğunu cevaplıyorum. Cevap da öyle. Cevabın ruhunu okumaya çalışın :-). Bilmesem itfa edilmiş yeniden tahsis ilk kapasite için herhangi bir değere sahip her durumda gerçekleşir ise ArrayList.
AmitG 15:03

2

Her ArrayList'in init kapasite değeri "10" ile oluşturulduğunu düşünüyorum. Her neyse, yapıcı içinde kapasite ayarlamadan bir ArrayList oluşturursanız, varsayılan bir değerle oluşturulur.


2

Onun bir optimizasyon olduğunu söyleyebilirim. Başlangıç ​​kapasitesi olmayan ArrayList'te ~ 10 boş satır bulunur ve bir ekleme yaptığınızda genişler.

Tam olarak öğe sayısını içeren bir listeye sahip olmak için trimToSize () öğesini çağırmanız gerekir


0

Deneyimlerime göre ArrayList, bir başlangıç ​​kapasitesi vermek yeniden tahsis maliyetlerinden kaçınmanın güzel bir yoludur. Ama bir uyarı var. Yukarıda belirtilen tüm öneriler, kişinin sadece eleman sayısının kabaca bir tahmini biliniyorsa başlangıç ​​kapasitesini sağlaması gerektiğini söylüyor. Ancak herhangi bir fikrim olmadan bir başlangıç ​​kapasitesi vermeye çalıştığımızda, ayrılan ve kullanılmayan bellek miktarı bir atık olacaktır, çünkü liste gerekli sayıda öğeye doldurulduktan sonra asla gerekli olmayabilir. Diyorum ki, kapasite tahsis ederken başlangıçta pragmatik olabilir ve daha sonra çalışma zamanında gerekli minimum kapasiteyi bilmenin akıllı bir yolunu bulabiliriz. ArrayList adlı bir yöntem sağlar ensureCapacity(int minCapacity). Ama sonra, akıllı bir yol buldu ...


0

ArrayList'i initialCapacity ile ve bu test olmadan test ettim ve şaşırtıcı bir sonuç elde
ettim

list1Sttop-list1Start = 14
list2Sttop-list2Start = 10


Ancak LOOP_NUMBER değerini 1.000.000 olarak ayarladığımda sonuç şu şekilde değişir:

list1Stop-list1Start = 40
list2Stop-list2Start = 66


Sonunda, nasıl çalıştığını anlayamadım ?!
Basit kod:

 public static final int LOOP_NUMBER = 100000;

public static void main(String[] args) {

    long list1Start = System.currentTimeMillis();
    List<Integer> list1 = new ArrayList();
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list1.add(i);
    }
    long list1Stop = System.currentTimeMillis();
    System.out.println("list1Stop-list1Start = " + String.valueOf(list1Stop - list1Start));

    long list2Start = System.currentTimeMillis();
    List<Integer> list2 = new ArrayList(LOOP_NUMBER);
    for (int i = 0; i < LOOP_NUMBER; i++) {
        list2.add(i);
    }
    long list2Stop = System.currentTimeMillis();
    System.out.println("list2Stop-list2Start = " + String.valueOf(list2Stop - list2Start));
}

Windows8.1 ve jdk1.7.0_80 üzerinde test yaptım


1
merhaba, ne yazık ki currentTimeMillis toleransı yüz milisaniyeye kadar (bağlı), bu da sonucun pek güvenilir olmadığı anlamına geliyor. Doğru yapmak için bazı özel kitaplıklar kullanmanızı öneririm.
Bogdan
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.