.toArray (yeni Sınıfım [0]) veya .toArray (yeni Sınıfım [myList.size ()])?


176

Bir ArrayListim olduğunu varsayarsak

ArrayList<MyClass> myList;

Ve Array'ı aramak istiyorum, kullanmak için bir performans nedeni var mı

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

bitmiş

MyClass[] arr = myList.toArray(new MyClass[0]);

?

Daha az ayrıntılı olduğu için ikinci stili tercih ediyorum ve derleyicinin boş dizinin gerçekten oluşturulmadığından emin olacağını varsaydım, ancak bunun doğru olup olmadığını merak ediyorum.

Tabii ki, vakaların% 99'unda şu veya bu şekilde bir fark yaratmaz, ancak normal kodum ve optimize edilmiş iç döngülerim arasında tutarlı bir stil tutmak istiyorum ...


6
Görünüşe göre soru şimdi Kadimlerin Bilgeliği Dizileri Aleksey Shipilёv'ın yeni bir blog gönderisine yerleştirildi !
16'da

6
Blog gönderisinden: "Alt satır: toArray (yeni T [0]) daha hızlı, daha güvenli ve sözleşmeye bağlı olarak daha temiz görünüyor ve bu nedenle şimdi varsayılan seçim olmalı."
DavidS

Yanıtlar:


109

Buna karşılık, Hotspot 8'deki en hızlı sürüm:

MyClass[] arr = myList.toArray(new MyClass[0]);

Sonuçları ve kod aşağıda, jmh kullanarak bir mikro benchmark çalıştırmak var boş bir dizi ile sürüm tutarlı bir şekilde bir dizi ile sürümden daha iyi gösteriyor. Var olan bir diziyi doğru boyutta yeniden kullanabiliyorsanız sonucun farklı olabileceğini unutmayın.

Deney sonuçları (mikrosaniye cinsinden puan, daha küçük = daha iyi):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025  0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155  0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512  0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884  0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147  0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977  5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019  0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133  0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075  0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318  0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652  0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692  8.957  us/op

Referans için kod:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

Benzer sonuçları, tam analizi ve tartışmayı Bloglar Dizilerinde Bilgili Diziler bölümünde bulabilirsiniz . Özetlemek gerekirse: JVM ve JIT derleyicisi, ucuz bir yeni doğru boyut dizisi oluşturmasını ve başlatmasını sağlayan çeşitli optimizasyonlar içerir ve diziyi kendiniz oluşturursanız bu optimizasyonlar kullanılamaz.


2
Çok ilginç bir yorum. Kimse bu konuda yorum yapmadı şaşırdım. Sanırım bunun nedeni, hız ile ilgili olarak buradaki diğer cevaplarla çelişiyor. Ayrıca dikkat edilmesi gereken bu adamların şöhreti, diğer tüm cevapların (ers) toplamından neredeyse daha yüksektir.
Pezevenk Trizkit

Konuya giriyorum. Ben MyClass[] arr = myList.stream().toArray(MyClass[]::new);de daha yavaş olacağını tahmin .. için kriterleri görmek istiyorum . Ayrıca, dizi bildirimi ile fark için kriterler görmek istiyorum. Arasındaki farkta olduğu gibi: MyClass[] arr = new MyClass[myList.size()]; arr = myList.toArray(arr);ve MyClass[] arr = myList.toArray(new MyClass[myList.size()]);... ya da herhangi bir fark olmamalı mı? Sanırım bu ikisi toArrayfonksiyonların dışında kalan bir konu . Ama hey! Diğer karmaşık farkları öğreneceğimi düşünmemiştim.
Pezevenk Trizkit

1
@PimpTrizkit Az önce kontrol edildi: fazladan bir değişken kullanmak beklendiği gibi bir fark yaratmaz, bir akış kullanmak toArraydoğrudan çağrı yapmak için% 60 ile% 100 arasında daha fazla zaman alır (boyut ne kadar küçükse, göreceli ek yük de o kadar büyük olur)
Assylias

Vay canına, bu hızlı bir yanıttı! Teşekkürler! Evet, bundan şüphelendim. Bir akışa dönüştürmek kulağa verimli gelmedi. Ama sen asla bilemezsin!
Pezevenk Trizkit


122

İtibariyle Java 5 ArrayList doğru boyutuna sahip (veya daha büyük) ise, dizi zaten doldurulacaktır. sonuç olarak

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

bir dizi nesnesi oluşturur, doldurur ve "arr" a döndürür. Diğer yandan

MyClass[] arr = myList.toArray(new MyClass[0]);

iki dizi oluşturacak. İkincisi, uzunluğu 0 olan bir MyClass dizisidir. Dolayısıyla, hemen atılacak bir nesne için bir nesne oluşturma vardır. Kaynak kodunun önerdiği gibi, derleyici / JIT bunu oluşturmamak için optimize edemez. Ayrıca, sıfır uzunluklu nesnenin kullanılması toArray () - yöntemi içinde döküm (ler) ile sonuçlanır.

ArrayList.toArray () kaynağına bakın:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Sadece bir nesne yaratılacak ve (örtük fakat yine de pahalı) dökümlerden kaçınacak şekilde ilk yöntemi kullanın.


1
İki yorum, birisinin ilgisini çekebilir: 1) LinkedList.toArray (T [] a) daha yavaştır (yansıma kullanır: Array.newInstance) ve daha karmaşık; 2) Öte yandan, JDK7 sürümünde, genellikle acı veren yavaş Array.newInstance'ın normal dizi oluşturma kadar hızlı performans gösterdiğini öğrenmek beni çok şaşırttı !
java.is.for.desktop

1
@ktaria size ArrayList'in özel bir üyesidir. Bkz ArrayList SourceCode
MyPasswordIsLasercats

3
Kıyaslama olmadan performansı tahmin etmek sadece önemsiz durumlarda işe yarar. Aslında, new Myclass[0]daha hızlı: shipilev.net/blog/2016/arrays-wisdom-ancients
Karol S

Bu, JDK6 + 'dan artık geçerli bir cevap değil
Антон Антонов

28

JetBrains Intellij Idea denetiminden:

Bir koleksiyonu diziye dönüştürmek için iki stil vardır: önceden boyutlandırılmış bir dizi ( c.toArray (yeni String [c.size ()] gibi) ) veya boş bir dizi ( c.toArray (new String [ 0]) .

Eski Java sürümlerinde, uygun boyutta bir dizi oluşturmak için gerekli olan yansıma çağrısı oldukça yavaş olduğundan, önceden boyutlandırılmış dizi kullanılması önerilir. Bununla birlikte, OpenJDK 6'nın geç güncellemelerinden bu yana, bu çağrı içselleştirildi ve boş dizi sürümünün performansını önceden boyutlandırılmış sürümle karşılaştırıldığında aynı ve bazen daha da iyi hale getirdi. Boyut ve toArray çağrısı arasında bir veri yarışı mümkün olduğundan, önceden boyutlandırılmış bir dizinin geçirilmesi tehlikelidir çünkü toplama işlemi sırasında eşzamanlı olarak daraltılmışsa, dizinin sonunda fazladan boşluğa neden olabilir.

Bu inceleme, tek biçimli stili izlemeye izin verir: boş bir dizi (modern Java'da önerilir) veya önceden boyutlandırılmış bir dizi (eski Java sürümlerinde veya HotSpot tabanlı olmayan JVM'lerde daha hızlı olabilir) kullanarak.


Tüm bunlar kopyalanmış / alıntılanmış bir metinse, metni buna göre biçimlendirebilir ve ayrıca kaynağa bir bağlantı sağlayabilir miyiz? Aslında buraya IntelliJ incelemesi nedeniyle geldim ve tüm denetimlerini ve bunların ardındaki mantığı aramak için bağlantıya çok ilgi duyuyorum.
Tim Büthe

3


17

Modern JVM'ler bu durumda yansıtıcı dizi yapısını optimize eder, bu nedenle performans farkı küçüktür. Koleksiyonu böyle bir kaynak kodunda iki kez adlandırmak harika bir fikir değil, bu yüzden ilk yöntemi kullanmam. İkincisinin bir başka avantajı, senkronize ve eşzamanlı koleksiyonlarla çalışmasıdır. Optimizasyon yapmak istiyorsanız, boş diziyi tekrar kullanın (boş diziler değiştirilemez ve paylaşılabilir) veya bir profiler (!) Kullanın.


2
'Boş diziyi yeniden kullan' seçeneğini yukarıdan kaldırın çünkü okunabilirlik ve dikkate alınması gereken potansiyel performans arasında bir uzlaşma. Bir argüman beyan geçen private static final MyClass[] EMPTY_MY_CLASS_ARRAY = new MyClass[0]yansıma ile inşa edilen dönen bir dizi engellemez, ancak etmez ek dizi her biri, her zaman inşa önlemek.
Michael Scheper

Machael haklıdır, sıfır uzunluklu bir dizi kullanırsanız , bunun bir yolu yoktur: (T []) java.lang.reflect.Array.newInstance (a.getClass (). GetComponentType (), size); boyut> = actualSize (JDK7)
Alex

"Modern JVM'ler yansıtıcı dizi yapısını bu durumda optimize eder" için bir alıntı yapabilirseniz, bu cevabı memnuniyetle destekleyeceğim.
Tom Panning

Burada öğreniyorum. Bunun yerine kullanırsam: MyClass[] arr = myList.stream().toArray(MyClass[]::new);Eşzamanlı ve eşzamanlı koleksiyonlara yardımcı olur veya incinir. Ve neden? Lütfen.
Pimp Trizkit

3

toArray, geçirilen dizinin doğru boyutta olup olmadığını (yani, listenizdeki öğelere sığacak kadar büyük) olup olmadığını denetler ve bunu kullanır. Sonuç olarak dizinin boyutu gerekenden daha küçükse, yeni bir dizi refleks olarak oluşturulur.

Sizin durumunuzda, sıfır büyüklüğünde bir dizi değiştirilemez, bu nedenle güvenli bir şekilde statik bir son değişkene yükseltilebilir, bu da kodunuzu biraz daha temiz hale getirebilir, bu da her çağrıda dizinin oluşturulmasını önler. Yine de yöntemin içinde yeni bir dizi oluşturulacak, bu nedenle okunabilirlik optimizasyonu.

Muhtemelen daha hızlı sürüm doğru bir boyuttaki diziyi geçmek, ancak bu kodun bir performans darboğu olduğunu kanıtlayamazsanız , aksi kanıtlanmadıkça çalışma zamanı performansına okunabilirliği tercih edin.


2

İlk durum daha verimlidir.

Çünkü ikinci durumda:

MyClass[] arr = myList.toArray(new MyClass[0]);

çalışma zamanı aslında boş bir dizi (sıfır boyutlu) oluşturur ve ardından toArray yönteminin içinde gerçek verilere uyacak başka bir dizi oluşturulur. Bu oluşturma aşağıdaki kod kullanılarak yansıma kullanılarak yapılır (jdk1.5.0_10):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

İlk formu kullanarak, ikinci bir dizi oluşturmaktan ve yansıma kodundan da kaçınabilirsiniz.


toArray () yansıma kullanmaz. En azından yansıma "döküm" saymazsanız, zaten ;-).
Georgi

toArray (T []) yapar. Uygun türde bir dizi oluşturmanız gerekir. Modern JVM'ler bu tür yansımayı yansıtmayan sürümle aynı hızda olacak şekilde optimize eder.
Tom Hawtin - tackline

Bence yansıma kullanıyor. JDK 1.5.0_10 kesin olarak yapar ve derleme zamanında bilmediğiniz türden bir dizi oluşturmayı bildiğim tek yol yansımadır.
Panagiotis Korros

Sonra kaynak kodlarından biri onu (yukarıdaki veya benimki) güncel değil. Ne yazık ki, benim için doğru bir alt sürüm numarası bulamadım.
Georgi

1
Georgi, kodunuz JDK 1.6'dan geliyor ve Arrays.copyTo yönteminin uygulanmasını görürseniz uygulamanın yansıma kullandığını göreceksiniz.
Panagiotis Korros

-1

İkincisi marjinal olarak okunabilir, ancak buna değmeyecek kadar az gelişme var. İlk yöntem daha hızlıdır, çalışma zamanında dezavantaj yoktur, bu yüzden kullanıyorum. Ama ikinci şekilde yazıyorum, çünkü yazmak daha hızlı. Sonra IDE'm bir uyarı olarak işaretler ve düzeltmeyi önerir. Tek bir tuşa basarak, kodu ikinci türden birinciye dönüştürür.


-2

'ToArray' öğesini doğru boyutta diziyle kullanmak, alternatif önce sıfır boyutlu diziyi sonra doğru boyutta diziyi oluşturacağından daha iyi performans gösterir. Ancak, dediğiniz gibi, farkın ihmal edilebilir olması muhtemeldir.

Ayrıca, javac derleyicisinin herhangi bir optimizasyon gerçekleştirmediğini unutmayın. Bu günlerde tüm optimizasyonlar çalışma zamanında JIT / HotSpot derleyicileri tarafından gerçekleştirilir. Herhangi bir JVMs 'toArray' etrafında herhangi bir optimizasyon farkında değilim.

O halde, sorunuzun cevabı büyük ölçüde bir stil meselesidir, ancak tutarlılık uğruna, uyduğunuz herhangi bir kodlama standardının (belgelenmiş veya başka türlü) bir parçasını oluşturmalıdır.


OTOH, standart sıfır uzunluklu bir dizi kullanacaksa, sapan durumlar performansın bir endişe kaynağı olduğunu ima eder.
Michael Scheper

-5

tamsayı için örnek kod:

Integer[] arr = myList.toArray(new integer[0]);
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.