Java'da Dizi veya Liste. Hangisi daha hızlı?


351

Java'da seri olarak erişilebilmesi için binlerce dizeyi bellekte tutmam gerekiyor. Bunları bir dizide saklamalı mıyım yoksa bir çeşit Liste mi kullanmalıyım?

Diziler tüm verileri bitişik bir bellek yığınında tuttuğundan (Listeler'den farklı olarak), binlerce dizeyi saklamak için bir dizinin kullanılması sorunlara neden olur mu?


5
"Diziler tüm verileri bitişik bir bellek yığınında tuttuğundan" Java için bunu destekleyecek alıntılarınız var mı?
matt b

1
Mat yok. Bunu C için biliyorum. Java'nın aynı yöntemi kullanacağını tahmin ediyorum.
euphoria83

Onları tek bir hafızada tutacağından şüpheliyim.
Fortyrunner

3
Tek bir bellek bloğu olsa bile, hala sadece 1000 * 4 = 4kb değerinde olurdu, bu da çok fazla bellek değildir.
CookieOfFortune

3
@mattb CS'de 'dizi' anlamı budur. Alıntıya gerek yok. JLS ve [JVM Spec] () 'deki dizi uzunluklarına yapılan çok sayıda referans yalnızca diziler bitişikse anlaşılabilir.
Lorne Marquis

Yanıtlar:


358

Daha hızlı olanı test etmek için bir profilleyici kullanmanızı öneririm.

Benim kişisel görüşüm Listeleri kullanmanız gerektiğidir.

Ben büyük bir kod tabanı üzerinde çalışıyorum ve önceki bir grup geliştiriciler her yerde diziler kullandı . Kodu çok esnek hale getirdi. Büyük parçalarını Listeler olarak değiştirdikten sonra hızda bir fark olmadığını fark ettik.


2
@Fortyrunner - Deneyimlerinizden, Java'da soyutlama ve ham veri formları arasında performansta önemli bir fark yaratan böyle seçenekler var mı?
euphoria83

4
Performans ölçümü ile ilgili sorunlardan biri, sürekli olarak Java'nın yeni sürümlerine karşı yeniden test yapmanız gerektiğidir. Birisi bir harita boyunca bir anahtar boyunca (yer / zaman kazanmak için) bir int kullandığım bir sorun üzerinde çalışıyorum. Şimdi tüm çizgileri yeni bir nesneye değiştirmeliyiz - acı verici.
Fortyrunner

9
Şimdi ham verilerden uzak durmaya çalışıyorum. Nadiren fark edilir bir fark yaratır. Hotspot inanılmaz bir teknoloji parçası ve asla denemek gerekir ve ikinci tahmin. Basit, bakımı kolay kod yazmaya çalışın, gerisini Hotspot halleder.
Fortyrunner

4
Profil oluşturucu sonuçlarının yalnızca profil oluşturucuyu çalıştırdığınız Java platformu için geçerli olduğunu unutmayın. Hangi müşterilerinizden farklı olabilir.
Mikkel Løkke

4
Etkili Java, API ile birlikte çalışabilirlik konusunda yardımcı oldukları ve tür güvenliğinde daha güvenli olduğu için Listeler önerir.
juanmf

164

Java yolu, hangi veri soyutlamanın ihtiyaçlarınıza en uygun olduğunu düşünmenizdir . Java'da bir Listenin somut bir veri türü değil, bir özet olduğunu unutmayın. Dizeleri bir Liste olarak bildirmeli ve sonra ArrayList uygulamasını kullanarak başlatmalısınız.

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

Özet Veri Türünün ve özel uygulamanın bu ayrımı, nesne yönelimli programlamanın anahtar yönlerinden biridir.

ArrayList, Liste Özeti Veri Türünü temel uygulama olarak bir dizi kullanarak uygular. Erişim hızı bir diziyle neredeyse aynıdır, ayrıca bir Listeye öğe ekleyip çıkarabilmenin ek avantajları (bu bir ArrayList ile O (n) işlemi olmasına rağmen) ve daha sonra temeldeki uygulamayı değiştirmeye karar verirseniz yapabilirsin. Örneğin, senkronize erişime ihtiyacınız olduğunu fark ederseniz, tüm kodunuzu yeniden yazmadan uygulamayı bir Vector olarak değiştirebilirsiniz.

Aslında, ArrayList, çoğu bağlamda düşük düzeyli dizi yapısının yerini almak üzere özel olarak tasarlanmıştır. Java bugün tasarlanıyorsa, dizilerin ArrayList yapısı lehine tamamen dışarıda bırakılması tamamen mümkündür.

Diziler tüm verileri bitişik bir bellek yığınında tuttuğundan (Listeler'den farklı olarak), binlerce dizeyi saklamak için bir dizinin kullanılması sorunlara neden olur mu?

Java'da, tüm koleksiyonlar nesnelerin kendilerine değil, yalnızca nesnelere yapılan başvuruları depolar. Hem diziler hem de ArrayList, bitişik bir dizide birkaç bin referans depolar, bu nedenle temelde aynıdırlar. Birkaç bin 32 bitlik referansın bitişik bir bloğunun her zaman modern donanımda mevcut olacağını düşünebilirsiniz. Bu, bellekte tamamen bitmeyeceğinizi garanti etmez, elbette, sadece bellek gereksiniminin bitişik bloğunun bulunması zor değildir.


Ekleme elbette destek dizisinin yeniden tahsis edilmesini içerebilir, bu nedenle performans önemliyse ve dizinin boyutu önceden biliniyorsa ArrayList # sureCapacity kullanmayı düşünmelisiniz.
JesperE

6
Burada dinamik bağlamanın maliyetini ödemiyor musunuz?
Uri

2
Ekleme ArrayList'te O (n) değil, birden fazla eklerken bazı amortisman etkisi olmalı, örn. Kapasite sadece 1 ile arttırılmış yerine kapasite iki katına
çıkacak

@zedoo Sanırım ortada toplama ve çıkarma demekti.
MalcolmOcean

"Java bugün tasarlanıyorsa, dizilerin ArrayList yapısı lehine tamamen dışarıda bırakılması tamamen mümkündür." ... bunun doğru olacağından şüpheliyim. Öyle olsaydı JVM yeniden ediliyor bugün, o zaman ne söylediğin kesinlikle mümkündür. Ancak sahip olduğumuz JVM ile, diziler Java'da temel bir türdür.
scottb

100

Her ne kadar ArrayList'i kullanmayı öneren cevaplar çoğu senaryoda mantıklı olsa da, göreceli performansla ilgili asıl soru gerçekten cevaplanmamıştır.

Bir dizi ile yapabileceğiniz birkaç şey vardır:

  • oluştur
  • bir eşya belirle
  • bir eşya al
  • kopyala / kopyala

Genel sonuç

Alma ve ayarlama işlemleri bir ArrayList'te biraz daha yavaş olsa da (makinemdeki arama başına sırasıyla 1 ve 3 nanosaniye), yoğun olmayan herhangi bir kullanım için ArrayList'e karşılık bir dizi kullanmanın çok az yükü vardır. Ancak akılda tutulması gereken birkaç şey vardır:

  • listedeki (arama yaparken list.add(...)) yeniden boyutlandırma işlemleri maliyetlidir ve kişi ilk kapasiteyi mümkün olduğunca yeterli bir seviyeye ayarlamaya çalışmalıdır (bir dizi kullanılırken aynı sorunun ortaya çıktığını unutmayın)
  • ilkellerle uğraşırken, diziler çok sayıda boks / kutulama dönüşümünden kaçınmaya izin vereceklerinden önemli ölçüde daha hızlı olabilir
  • ArrayList'te (çok yaygın değil!) değerleri alan / ayarlayan bir uygulama, bir diziye geçerek% 25'ten fazla bir performans artışı görebilir

Ayrıntılı sonuçlar

Standart bir x86 masaüstü makinesinde JDK 7 ile jmh karşılaştırma kütüphanesini (nanosaniye cinsinden zamanlar) kullanarak bu üç işlem için ölçtüğüm sonuçlar . Sonuçların karşılaştırılabilir olduğundan emin olmak için testlerde ArrayList öğesinin hiçbir zaman yeniden boyutlandırılmadığını unutmayın. Karşılaştırma kodu burada bulunabilir .

Array / ArrayList Oluşturma

Aşağıdaki ifadeleri uygulayarak 4 test yaptım:

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: List<Integer> list = new ArrayList<> (10000);

Sonuçlar (arama başına nanosaniye cinsinden,% 95 güven):

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]

Sonuç: gözle görülür bir fark yok .

operasyon almak

Aşağıdaki ifadeleri uygulayarak 2 test yaptım:

  • GetList: return list.get(0);
  • GetArray: return array[0];

Sonuçlar (arama başına nanosaniye cinsinden,% 95 güven):

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

Sonuç: bir diziden almak, bir ArrayList'ten almaktan yaklaşık% 25 daha hızlıdır , ancak fark sadece bir nanosaniye civarındadır.

operasyonları ayarla

Aşağıdaki ifadeleri uygulayarak 2 test yaptım:

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

Sonuçlar (arama başına nanosaniye cinsinden):

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

Sonuç: diziler üzerinde ayarlanan işlemler listelerden yaklaşık% 40 daha hızlıdır , ancak elde edileceği gibi, her ayar işlemi birkaç nanosaniye sürer - bu nedenle farkın 1 saniyeye ulaşması için liste / dizi yüzlerce öğenin ayarlanması gerekir milyonlarca kez!

klon / kopyala

İçin ArrayList'ın kopya yapıcı delegeler Arrays.copyOfperformans dizisi kopyalama ile aynıdır, böylece (vasıtasıyla bir dizi kopyalama clone, Arrays.copyOfya da System.arrayCopy herhangi bir malzeme farkı epeyce yapar ).


1
Güzel analiz. Ancak, yorumunuzla ilgili olarak, "ilkellerle uğraşırken, diziler, bir çok boks / kutulama dönüşümünden kaçınmasına izin verecekleri için önemli ölçüde daha hızlı olabilir ", ilkel dizi destekli bir listeyle pastanızı alabilir ve yiyebilirsiniz. uygulanması; örneğin: github.com/scijava/scijava-common/blob/master/src/main/java/org/… . Aslında böyle bir şeyin çekirdek Java'ya girmediğine oldukça şaşırdım.
ctrueden

2
@ctrueden evet standart JDK ArrayList'e uygulanan yorum. trove4j, ilkel listeleri destekleyen iyi bilinen bir kütüphanedir. Java 8, birkaç ilkel özel Akış ile bazı iyileştirmeler getiriyor.
assylias

JMh ölçütlerinin nasıl çalıştığını bilmiyorum ama olabilecek JIT derlemesini dikkate alıyorlar mı? JVM kodunuzu derledikçe bir Java uygulamasının performansı zaman içinde değişebilir.
Hoffmann

@Hoffmann Evet - ölçümden hariç tutulan bir ısınma aşaması içerir.
assylias

97

Dizilere göre genel türleri tercih etmelisiniz. Diğerleri tarafından belirtildiği gibi, diziler esnek değildir ve jenerik tiplerin ifade gücüne sahip değildir. (Ancak çalışma zamanı yazım denetimini desteklerler, ancak bu genel türlerle kötü bir şekilde karışır.)

Ancak, her zaman olduğu gibi, optimize ederken her zaman şu adımları izlemelisiniz:

  • Kodunuzun güzel, temiz ve çalışan bir sürümüne sahip olana kadar optimizasyon yapmayın . Jenerik tiplere geçiş zaten bu adımda çok iyi motive edilebilir.
  • Güzel ve temiz bir sürüme sahip olduğunuzda, yeterince hızlı olup olmadığına karar verin.
  • Yeterince hızlı değilse performansını ölçün . Bu adım iki nedenden dolayı önemlidir. Ölçmezseniz (1) yaptığınız optimizasyonların etkisini ve (2) nerede optimizasyon yapacağınızı bilmeyeceksiniz.
  • Kodunuzun en sıcak kısmını optimize edin.
  • Tekrar ölçün. Bu, daha önce ölçmek kadar önemlidir. Optimizasyon bazı şeyleri iyileştirmediyse geri alın . Unutmayın, optimizasyon olmadan kod temiz, güzel ve çalışıyor.

24

Orijinal posterin biraz karışıklığa neden olan bir C ++ / STL arka planından geldiğini tahmin ediyorum. C ++ std::listile çift bağlantılı bir listedir.

Java'da [java.util.]List, uygulama gerektirmeyen bir arayüzdür (C ++ terimleriyle saf soyut sınıf). Listçift ​​bağlantılı bir liste olabilir - java.util.LinkedListsağlanır. Ancak, yeni bir yapmak istediğinizde 100'ün 99'u List, java.util.ArrayListbunun yerine kullanmak istersiniz , bu da C ++ 'nın kaba eşdeğeri std::vector. java.util.Collections.emptyList()Ve tarafından döndürülenler gibi başka standart uygulamalar da vardır java.util.Arrays.asList().

Performans açısından bakıldığında, bir arabirimden ve ekstra bir nesneden geçmek zorunda kalmanın çok küçük bir vuruşu vardır, ancak çalışma zamanı satır içi ayarı bunun nadiren herhangi bir önemi olduğu anlamına gelir. Ayrıca Stringgenellikle bir nesne artı dizi olduğunu unutmayın . Yani her bir giriş için muhtemelen iki nesneniz daha var. C ++ 'da std::vector<std::string>, bir işaretçi olmadan değere göre kopyalansa da, karakter dizileri dize için bir nesne oluşturur (ve bunlar genellikle paylaşılmaz).

Bu özel kod gerçekten performansa duyarlıysa, tüm dizelerin karakterleri için tek bir char[]dizi (hatta çift byte[]) ve sonra bir dizi ofset oluşturabilirsiniz. IIRC, javac bu şekilde uygulanır.


1
Cevap için teşekkürler. Ama hayır, C ++ listesini Java'nın arayüz listesi ile karıştırmıyorum. Soruyu öyle bir şekilde sordum çünkü ArrayList ve Vector gibi Liste uygulamalarının performansını ham dizilerle karşılaştırmak istedim.
euphoria83

Hem ArrayList hem de Vector "tüm verileri bitişik bir bellek yığınında tutar".
Tom Hawtin - tackline

13

Çoğu durumda ArrayLists'in diziler üzerindeki esnekliğini ve zarafetini seçmeniz gerektiğini kabul ediyorum - ve çoğu durumda program performansına olan etkinin önemsiz olacağını kabul ediyorum.

Ancak sabiti, küçük yapısal değişiklik ile ağır yinelemeyi yapıyorsan, (hayır ekler ve kaldırır), için diyelim ki, yazılım grafik özel sanal makine render ya da benim sıralı erişim benchmarking testleri gösteriyor Dizi Listeleri diziler daha 1.5x yavaştır üzerinde benim sistemi (bir yaşındaki iMac'imde Java 1.6).

Bazı kodlar:

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

Bu ilginç bir cevap buldum, ancak ArrayList'in bellekte bir başlangıç ​​boyutuyla başlatılmamış olmasının daha da kötü olup olmadığını merak ediyorum. Genel olarak ArrayList'i yerel anlamda bir dizi üzerinde kullanmanın yararı, bilmeyeceğiniz ve endişelenmenize gerek olmamasıdır. ArrayLists varsayılan olarak başlangıç ​​uzunluğu 10 ile oluşturulur ve daha sonra yeniden boyutlandırılır. Bence yeniden boyutlandırma pahalı. Açıkçası karşılaştırmayı denemedim.
Zak Patterson

4
Bu mikro karşılaştırmanın kusurları var (ısınma yok, işlemler ayrı bir yöntemde değil, bu yüzden arraylist kısım asla JIT tarafından optimize edilmedi vb.)
Asurlar

Asililerle hemfikirim. Bu kriterin sonuçlarına güvenilmemelidir.
Stephen C

@StephenC Uygun bir mikro karşılaştırma ölçütü ekledim (get işlemlerinin karşılaştırılabilir olduğunu gösterir).
assylias

11

Öncelikle açıklamakta fayda var klasik comp sci veri yapıları anlamında "liste" demek (yani bağlantılı bir liste) veya java.util.List demek? Bir java.util.List demek istiyorsanız, bu bir arabirimdir. Bir dizi kullanmak istiyorsanız ArrayList uygulamasını kullanın ve dizi benzeri davranış ve anlambilim elde edin. Sorun çözüldü.

Bağlantılı bir listeye karşılık bir dizi kastediyorsanız, Big O'ya geri döndüğümüzden biraz farklı bir argüman (işte bu, yabancı bir terim ise basit bir İngilizce açıklamasıdır .

Dizi;

  • Rastgele Erişim: O (1);
  • Ekleme: O (n);
  • Sil: O (n).

Bağlantılı liste:

  • Rastgele Erişim: O (n);
  • Ekleme: O (1);
  • Sil: O (1).

Böylece dizinizi nasıl yeniden boyutlandıracağınıza en uygun olanı seçersiniz. Yeniden boyutlandırırsanız, çok şey ekleyip silerseniz, bağlantılı bir liste daha iyi bir seçim olabilir. Rastgele erişim nadirse aynı durum geçerlidir. Seri erişimden bahsediyorsunuz. Çoğunlukla çok az modifikasyonla seri erişim yapıyorsanız, hangisini seçtiğiniz önemli değil.

Bağlantılı listelerin yükü biraz daha yüksektir, çünkü dediğiniz gibi, potansiyel olarak bitişik olmayan bellek blokları ve bir sonraki öğeye (etkin olarak) işaretçilerle uğraşıyorsunuz. Ancak, milyonlarca girişle uğraşmadıkça bu önemli bir faktör değildir.


java.util.List interface
euphoria83

1
Bağlantılı listedeki rastgele erişim O (n) benim için çok önemli gibi görünüyor.
Bjorn

11

ArrayLists'i Arrays ile karşılaştırmak için küçük bir kıyaslama yazdım. Eski dizüstü bilgisayarımda, 5000 elemanlı bir arilistten geçme süresi, 1000 kez, eşdeğer dizi kodundan yaklaşık 10 milisaniye daha yavaştı.

Yani, listeyi yinelemekten başka bir şey yapmıyorsanız ve çok fazla yapıyorsanız, belki de optimizasyona değer. Aksi takdirde List'i kullanırım, çünkü kodu optimize etmeniz gerektiğinde daha kolay hale getirecektir .

nb ı did kullanarak o haber for String s: stringsListerişmek listesi For döngüsü eski stilini kullanarak yaklaşık% 50 daha yavaş olmuştur. Git şekil ... İşte zamanladığım iki fonksiyon; dizi ve liste 5000 rastgele (farklı) dizeyle dolduruldu.

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: Harika iş çıkardın! Her ikisi için gerçek çalışma süreleri nelerdir? Kullandığınız dizelerin boyutunu söyleyebilir misiniz? Ayrıca, 'String s: stringsList' kullanımı daha uzun sürdüğünden, genel olarak Java'daki daha yüksek soyutlamaları kullanmaktaki birincil korkum budur.
euphoria83

Bu mcirobenchmark için dizelerin ne kadar uzun olduğu önemli değildir. Gc yoktur ve char[]dokunulmaz (bu C değildir).
Tom Hawtin - tackline 04

Benim için tipik zamanlar dizi sürümü için ~ 25ms, ArrayList sürümü için ~ 35ms idi. Dizeler 15-20 karakter uzunluğundaydı. Tom'un dediği gibi, dize boyutu çok fazla bir fark yaratmaz, ~ 100 karakterlik bir dize ile zamanlamalar yaklaşık olarak aynıydı.
Chris Mayıs

3
Nasıl ölçtün? Java mikro ölçütlerinde saf ölçüm genellikle bilgiden daha fazla yanlış bilgi üretir. Yukarıdaki ifadeye dikkat edin.
jmg

6

Hayır, çünkü teknik olarak, dizi yalnızca dizelere başvuruyu depolar. Dizelerin kendileri farklı bir yerde tahsis edilir. Bin ürün için, bir listenin daha iyi olacağını, daha yavaş olduğunu söyleyebilirim, ancak daha fazla esneklik sunar ve özellikle bunları yeniden boyutlandıracaksanız, kullanımı daha kolaydır.


5
Liste ayrıca yalnızca dizelere referansı saklar.
Peter Štibraný

6

Binlerce varsa trie kullanmayı düşünün. Üçgen, saklanan dizenin ortak öneklerini birleştiren ağaç benzeri bir yapıdır.

Örneğin, dizeler

intern
international
internationalize
internet
internets

Üçlü depolar:

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

Dizeler, depolama için 57 karakter (null sonlandırıcı, '\ 0' dahil) ve bunları içeren String nesnesinin boyutu ne olursa olsun gerektirir. (Gerçekte, tüm boyutları 16'nın katlarına kadar yuvarlamalıyız, ama ...) Kabaca 57 + 5 = 62 bayt diyelim.

Üçgen, depolama için 29 (boş sonlandırıcı, '\ 0' dahil) ve bir diziye ref ve alt üçlü düğümlerin bir listesi olan üçlü düğümlerin boyutlarını gerektirir.

Bu örnek için, muhtemelen aynı ortaya çıkıyor; binlerce kişi için, yaygın öneklere sahip olduğunuz sürece muhtemelen daha az ortaya çıkar.

Şimdi, trie'yi başka bir kodda kullanırken, muhtemelen bir StringBuffer'ı aracı olarak kullanarak String'e dönüştürmeniz gerekir. Dizelerin birçoğu aynı anda Dizeler olarak kullanılıyorsa, trie dışında bu bir kayıptır.

Ancak o sırada yalnızca birkaç tanesini kullanıyorsanız - örneğin, bir sözlükteki şeyleri aramak için - trie size çok fazla yer kazandırabilir. Kesinlikle bir HashSet içinde saklamaktan daha az alan.

Onlara "seri olarak" eriştiğinizi söylüyorsunuz - bu, sırayla alfabetik olarak anlamına geliyorsa, trie ayrıca, önce derinlik yinelerseniz, size ücretsiz olarak alfabetik düzen de verir.


1
trie bir kütüphane gibi mi ya da nasıl oluştururum?
euphoria83

Bir üçlü, yalnızca çalışan dizeleri dize olarak saklıyorsa değil, yalnızca dizgilenmiş dizeler için yararlı olur.
MN

5

GÜNCELLEME:

Mark'ın belirttiği gibi, JVM ısındıktan sonra önemli bir fark yoktur (birkaç test geçişi). Yeniden oluşturulan dizi veya hatta yeni matris satırından başlayarak yeni geçiş ile kontrol edildi. Büyük olasılıkla bu işaretler dizin erişimine sahip basit bir dizi koleksiyonlar lehine kullanılmaz.

Hala ilk 1-2 geçer basit dizi 2-3 kat daha hızlıdır.

ORİJİNAL POST:

Konu için çok fazla kelime kontrol etmek çok basit. Herhangi bir soru dizisi olmadan herhangi bir sınıf kapsayıcısından birkaç kat daha hızlıdır . Bu soru üzerinde performans açısından kritik bölümüm için alternatifler arıyorum. İşte gerçek durumu kontrol etmek için inşa ettiğim prototip kodu:

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

Ve işte cevap:

Diziye göre (satır 16 aktif):

Time: 7064

Listeye göre (17. satır etkin):

Time: 20950

'Daha hızlı' hakkında daha fazla yorumunuz var mı? Bu oldukça anlaşılır. Soru, Liste esnekliğinden yaklaşık 3 kat daha hızlı sizin için daha iyi olduğunda. Ama bu başka bir soru. Bu arada ben de elle inşa dayalı bu kontrol ArrayList. Neredeyse aynı sonuç.


2
3kat daha hızlı doğru, ama önemsiz. 14msuzun bir süre değil
0x6C38

1
Benchmark JVM ısınmasını düşünmüyor. Main () öğesini test () olarak değiştirin ve testi tekrar tekrar main'den çağırın. Testin 3. veya 4. çalışmasıyla, birçok kez daha hızlı çalışır. Bu noktada, dizinin diziden yaklaşık 9 kat daha hızlı olduğunu görüyorum.
Mike

5

Burada zaten çok iyi yanıtlar olduğundan, ekleme ve yineleme performansı karşılaştırması olan pratik görünümün diğer bazı bilgilerini vermek istiyorum : Java'da ilkel dizi vs Bağlantılı liste.

Bu gerçek basit performans kontrolüdür.
Dolayısıyla, sonuç makine performansına bağlı olacaktır.

Bunun için kullanılan kaynak kodu aşağıdadır:

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

Performans Sonucu aşağıdadır:

resim açıklamasını buraya girin


4

Verimliliğe ihtiyacınız varsa dizileri kullanın.Eğer esnekliğe ihtiyacınız varsa listeyi kullanın.


4

Bir ArrayList öğesinin bir diziyi kapsadığını unutmayın, bu nedenle ilkel dizi kullanmaya kıyasla çok az fark vardır (bir Listenin java ile çalışmanın çok daha kolay olması dışında).

Bir diziyi bir ArrayList'e tercih etmenin mantıklı olduğu tek şey, ilkelleri, yani bayt, int, vb.


4

Dizi ve Liste seçimi, dize nesnelerinin depolanması durumunda (performans dikkate alındığında) o kadar önemli değildir. Dizi ve liste hem gerçek nesneleri değil, dize nesnesi referanslarını depolayacaktır.

  1. Dize sayısı neredeyse sabitse, bir dizi (veya ArrayList) kullanın. Ancak sayı çok fazla değişirse, LinkedList'i kullanmanız daha iyi olur.
  2. Ortada öğe ekleme veya silme ihtiyacı varsa (veya olacaksa), kesinlikle LinkedList'i kullanmanız gerekir.

4

Listeleri diziler üzerinde kullanmanın performans üzerindeki etkisi hakkında daha iyi bir his elde etmek için buraya geldim. Benim senaryo için kod burada uyarlamak zorunda kaldı: dizi / çoğunlukla getters kullanarak ~ 1000 ints listesi, dizi [j] vs list.get (j)

Bu konuda bilgisiz olmak için en iyi 7 (2.5x daha yavaş olduğu liste ile ilk birkaç) alarak:

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

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

- yani, dizi ile kabaca% 30 daha hızlı

Şimdi göndermenin ikinci nedeni, iç içe döngülerle matematik / matris / simülasyon / optimizasyon kodu yaparsanız kimsenin etkiden bahsetmemesi .

İç içe geçmiş üç seviyeniz olduğunu ve iç döngünün 8 kat daha fazla performansa baktığınızdan iki kat daha yavaş olduğunu varsayalım. Bir günde çalışacak bir şey şimdi bir hafta sürüyor.

* EDIT Oldukça şok oldu, tekmeler için Integer [1000] yerine int [1000] bildirmeye çalıştım

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

Integer [] ve int [] kullanılması çift performans vuruşunu temsil eder, yineleyicili ListArray int [] 'den 3 kat daha yavaştır. Java'nın liste uygulamalarının yerel dizilere benzediğini gerçekten düşündüm ...

Referans kodu (birden çok kez arayın):

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

3

Verilerin ne kadar büyük olduğunu önceden biliyorsanız, bir dizi daha hızlı olacaktır.

Liste daha esnektir. Bir dizi tarafından desteklenen bir ArrayList kullanabilirsiniz.


ArrayList, yedekleme dizisini belirtilen boyuta önceden yerleştiren bir sureCapacity () yöntemine sahiptir.
JesperE

Veya inşaat zamanında boyutu belirleyebilirsiniz. Ayrıca "burada" daha hızlı "bir yerine iki bellek alanı ayırmak için birkaç mikrosaniye anlamına gelir"
Aaron Digulla

3

Sabit bir boyutla yaşayabilirsiniz, diziler daha hızlı olacak ve daha az belleğe ihtiyaç duyacaktır.

Liste arabiriminin, öğe ekleme ve kaldırma esnekliğine ihtiyacınız varsa, hangi uygulamayı seçmeniz gerektiği sorusu kalır. Genellikle ArrayList önerilir ve her durumda kullanılır, ancak listenin başında veya ortasındaki öğelerin kaldırılması veya eklenmesi gerekiyorsa ArrayList'in performans sorunları da vardır.

Bu nedenle GapList'i tanıtan http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list'e göz atmak isteyebilirsiniz. Bu yeni liste uygulaması, hem ArrayList hem de LinkedList'in güçlü yönlerini birleştirerek neredeyse tüm işlemler için çok iyi performans sağlar.


2

Uygulamaya bağlı olarak. bir dizi ilkel türün ArrayList'ten daha küçük ve daha verimli olması mümkündür. Bunun nedeni, dizinin değerleri doğrudan bitişik bir bellek bloğunda depolamasıdır, en basit ArrayList uygulaması ise her değere işaretçiler depolayacaktır. Özellikle 64 bit bir platformda, bu büyük bir fark yaratabilir.

Elbette, jvm uygulamasının bu durum için özel bir duruma sahip olması mümkündür, bu durumda performans aynı olacaktır.


2

Liste, java 1.5 ve ötesinde jenerikleri kullanabileceği için tercih edilen yoldur. Dizilerin jenerikleri olamaz. Ayrıca Diziler, dinamik olarak büyüyemeyen önceden tanımlanmış bir uzunluğa sahiptir. Büyük boyutlu bir diziyi başlatmak iyi bir fikir değildir. ArrayList jeneriklerle bir dizi bildirmenin yoludur ve dinamik olarak büyüyebilir. Ancak sil ve ekle daha sık kullanılırsa, bağlantılı liste kullanılacak en hızlı veri yapısıdır.


2

Özellikle öğe sayısı ve boyutunun değişmeyeceğini biliyorsanız, liste yerine kullanabileceğiniz her yerde önerilen diziler.

Oracle Java en iyi uygulamalarına bakın: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

Tabii ki, toplama eklemek ve kaldırmak nesneler birçok kez kolay kullanım listeleri.


Bağlandığınız belgeler 10 yıldan daha eskidir, yani java 1.3 için geçerlidir. O zamandan beri önemli performans iyileştirmeleri yapıldı ...
assylias

@assylias yukarıdaki cevapları görüyorlar, performans testlerini içeriyorlar, bu da dizilerin daha hızlı olduğunu söylüyor
Nik

3
Onlardan birini yazdığımı biliyorum. Ancak " listeler yerine bunları kullanabileceğiniz her yerde diziler tavsiye edilir" iyi bir tavsiye olduğunu düşünmüyorum . Temel öğelerle uğraşmazsanız ve kodunuz performansa duyarlı değilse ArrayList çoğu durumda varsayılan seçim olmalıdır.
assylias

2

Cevapların hiçbiri ilgilendiğim bilgiye sahip değildi - aynı dizinin defalarca taranması. Bunun için bir JMH testi oluşturmak zorunda kaldım.

Sonuçlar (Java 1.8.0_66 x32, yinelenen düz dizi, ArrayList'ten en az 5 kat daha hızlıdır):

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

Ölçek

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

}

2

"Binlerce" çok sayıda değil. Birkaç bin paragraf uzunluğundaki dizeler birkaç megabayt büyüklüğündedir. Tek yapmanız gereken bunlara seri olarak erişmekse, değişmez tek bağlantılı bir Liste kullanın .


Çoğu 64 bit uygulamada 8 bayt.
Tom Hawtin - tackline

Bu şeyin java.util.LinkedList'ten daha hızlı olduğuna dair bir kanıt var mı? Hangisi aynı zamanda 'bellekte'? Bu, herhangi bir fark yaratıyormuş gibi değişmez hale getirilebilir.
Lorne Marquis

1

Uygun bir kıyaslama yapmadan optimizasyon tuzağına girmeyin. Diğerlerinin önerdiği gibi, herhangi bir varsayım yapmadan önce bir profiler kullanın.

Numaralandırdığınız farklı veri yapılarının farklı amaçları vardır. Bir liste, başlangıçta ve sonunda öğeleri eklemede çok etkilidir, ancak rastgele öğelere erişirken çok fazla acı çeker. Bir dizi sabit depolama alanına sahiptir, ancak hızlı rasgele erişim sağlar. Son olarak, bir ArrayList, bir dizinin büyümesine izin vererek arayüzü geliştirir. Normalde kullanılacak veri yapısı, depolanan verilerin nasıl erişileceği veya ekleneceği ile belirlenmelidir.

Bellek tüketimi hakkında. Bazı şeyleri karıştırıyor gibisin. Bir dizi yalnızca sahip olduğunuz veri türü için sürekli bir bellek yığını sağlar. Java'nın sabit veri türlerine sahip olduğunu unutmayın: boolean, char, int, long, float ve Object (buna tüm nesneler dahildir, hatta bir dizi bile bir Nesnedir). Dize dizeleri [1000] veya MyObject myObjects [1000] dizisini bildirirseniz, nesnelerin konumunu (başvurular veya işaretçiler) depolayacak kadar büyük bir 1000 bellek kutusu alırsınız. Nesnelerin boyutuna sığacak kadar büyük 1000 bellek kutusu almazsınız. Nesnelerinizin ilk önce "yeni" ile oluşturulduğunu unutmayın. Bu, bellek tahsisi yapıldığında ve daha sonra dizide bir referans (bellek adresleri) saklanır. Nesne diziye kopyalanmaz, sadece referanstır.


1

Dizeler için gerçek bir fark yarattığını sanmıyorum. Bir dizgide bitişik olan şey dizelere yapılan göndermelerdir, dizelerin kendileri bellekte rastgele yerlerde saklanır.

Diziler ve Listeler, nesneler için değil, ilkel türler için fark yaratabilir. EĞER önceden elemanların sayısını biliyoruz ve esneklik gerekmez aslında onlar bitişik saklanacak ve anında erişilebilir, çünkü tamsayılar veya çiftlerde milyonlarca dizisi, bellekte ve marjinal bir liste daha hız daha verimli olacaktır. Bu yüzden Java hala dizeler için karakter dizileri, görüntü verileri için veri dizileri vb.



1

Burada verilen bir çok mikrobenç işaret, dizi / ArrayList okumaları gibi şeyler için birkaç nanosaniye sayı bulmuştur. Her şey L1 önbelleğinizde varsa bu oldukça makul.

Daha yüksek seviyeli bir önbellek veya ana bellek erişimi, 10nS-100nS gibi bir şeyin büyüklük zamanlarına, L1 önbellek için 1nS'ye benzeyebilir. Bir ArrayList'e erişmek için fazladan bir bellek indirimi vardır ve gerçek bir uygulamada, kodunuzun erişimler arasında ne yaptığına bağlı olarak bu maliyeti neredeyse hiçbir zaman her zaman ödeyebilirsiniz. Ve elbette, çok sayıda küçük ArrayLists'iniz varsa, bu, bellek kullanımınıza katkıda bulunabilir ve önbellek özledikleriniz olma olasılığını artırabilir.

Orijinal poster sadece bir tane kullanıyor ve kısa sürede çok sayıda içeriğe erişiyor gibi görünüyor, bu yüzden büyük bir zorluk olmamalı. Ancak diğer insanlar için farklı olabilir ve mikrobenç işaretleri yorumlarken dikkat etmelisiniz.

Bununla birlikte, Java Dizeleri, özellikle çok sayıda küçük olanı depolarsanız (sadece bir bellek analizörü ile baktığınızda, birkaç karakterden oluşan bir dizi için> 60 bayt gibi görünüyor) son derece israflıdır. Bir dizgi dizesinin String nesnesine bir dolaylaması ve String nesnesinden dizenin kendisini içeren bir char [] öğesine başka bir dolaylaması vardır. L1 önbelleğinizi patlatacak bir şey varsa, bu binlerce veya on binlerce Dizeyle birlikte. Bu nedenle, mümkün olduğunca çok performans kazıma konusunda ciddi iseniz - gerçekten ciddi iseniz - farklı bir şekilde yapmaya bakabilirsiniz. Diyelim ki iki diziyi, içinde tüm dizeleri olan bir char [] ve birbirini takip eden bir int [] tutabilirsiniz. Bu bir şey yapmak için bir PITA olacak ve neredeyse kesinlikle buna ihtiyacınız yok. Ve eğer yaparsan, sen


0

Nasıl erişmeniz gerektiğine bağlıdır.

Kaydettikten sonra, esas olarak ekleme / silme işlemi çok az veya hiç olmadan arama işlemi yapmak istiyorsanız Array'a gidin (arama dizilerde O (1) 'de yapıldığından, ekleme / silme öğelerin yeniden sıralanması gerekebilir) .

Kaydettikten sonra, asıl amacınız arama işlemi çok az veya hiç olmadan dize eklemek / silmekse, Liste'ye gidin.


0

ArrayList, öğeleri eklemek (veya depolamak) için dahili olarak dizi nesnesini kullanır. Başka bir deyişle, ArrayList, Array veri -yapısı tarafından desteklenir.ArrayList dizisi yeniden boyutlandırılabilir (veya dinamik).

ArrayList dahili olarak dizi kullandığından Array, Array'den daha hızlıdır . ArrayList üzerinden doğrudan Array öğelerini ekleyebilir ve ArrayList aracılığıyla dolaylı olarak Array öğesine öğe ekleyebilirsek, her zaman doğrudan mekanizma dolaylı olarak mekanizmadan daha hızlıdır.

ArrayList sınıfında aşırı yüklenmiş iki add () yöntemi vardır:
1 add(Object) .: listenin sonuna nesne ekler.
2 add(int index , Object ) .: belirtilen nesneyi listede belirtilen konuma ekler.

ArrayList'in boyutu dinamik olarak nasıl büyür?

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

Yukarıdaki koddan dikkat edilmesi gereken önemli nokta, öğeyi eklemeden önce ArrayList'in kapasitesini kontrol etmemizdir. sureCapacity (), kullanılan öğelerin geçerli boyutunun ve dizinin maksimum boyutunun ne olduğunu belirler. Doldurulmuş öğelerin boyutu (ArrayList sınıfına eklenecek yeni öğe dahil) dizinin maksimum boyutundan büyükse dizinin boyutunu artırın. Ancak dizinin boyutu dinamik olarak artırılamaz. Dahili olarak yeni Array'ın kapasite ile yaratılması

Java 6'ya kadar

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

(Güncelleme) Java 7'den

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

ayrıca eski dizideki veriler yeni diziye kopyalanır.

ArrayList'te genel yöntemler kullanmak, Array'dan daha hızlıdır ArrayList.


0

Diziler - Sonuçların daha hızlı getirilmesini sağlamak her zaman daha iyi olurdu

Listeler- O (1) 'de yapılabildikleri için ekleme ve silme işlemlerinde sonuçları gerçekleştirir ve bu da kolayca veri ekleme, alma ve silme yöntemleri sağlar. Kullanımı çok daha kolay.

Ancak, verilerin saklandığı dizideki dizin konumu bilindiğinde, verilerin getirilmesinin hızlı olacağını her zaman unutmayın.

Bu, diziyi sıralayarak başarılı olabilir. Dolayısıyla bu, veri getirme süresini artırır (yani, verileri saklama + verileri sıralama + verilerin bulunduğu konumu arama). Bu nedenle, veriyi daha erken getirmekte iyi bile olsa, diziden veri almak için ek gecikme süresi artar.

Dolayısıyla bu, üçlü veri yapısı veya üçlü veri yapısı ile çözülebilir. Yukarıda tartışıldığı gibi, üç boyutlu veri yapısı, verilerin aranmasında çok etkili olacaktır, özellikle O (1) büyüklüğünde bir kelime araştırması yapılabilir. Zaman önemli olduğunda; hızlı bir şekilde veri aramak ve almak zorundaysanız, üçlü veri yapısı ile gidebilirsiniz.

Bellek alanınızın daha az tüketilmesini ve daha iyi bir performans elde etmek istiyorsanız üçlü veri yapısı ile devam edin. Her ikisi de çok sayıda dizeyi (ör. Sözlükte bulunan kelimeler gibi) depolamak için uygundur.

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.