Matrice o elenco in Java. Qual è più veloce?


351

Devo mantenere migliaia di stringhe in memoria per accedervi in ​​serie in Java. Devo memorizzarli in un array o utilizzare un tipo di elenco?

Dato che gli array mantengono tutti i dati in un pezzo contiguo di memoria (a differenza degli elenchi), l'uso di un array per archiviare migliaia di stringhe causerebbe problemi?


5
"Dato che le matrici conservano tutti i dati in un pezzo contiguo di memoria", hai qualche tipo di citazione per eseguire il backup di questo per Java?
opaco b

1
No opaco. Lo so per C. Immagino che Java userebbe lo stesso metodo.
euphoria83

Dubito che li terrebbe in un unico pezzo di memoria.
Fortyrunner,

3
Anche se si tratta di un singolo blocco di memoria, avrebbe comunque un valore di circa 1000 * 4 = 4kb, che non è un sacco di memoria.
CookieOfFortune

3
@mattb Questo è il significato di 'array' in tutto il CS. Nessuna citazione necessaria. I numerosi riferimenti in JLS e [JVM Spec] () alle lunghezze di array sono comprensibili solo se gli array sono contigui.
Marchese di Lorne,

Risposte:


358

Ti suggerisco di utilizzare un profiler per testare quale è più veloce.

La mia opinione personale è che dovresti usare le liste.

Lavoro su una base di codice di grandi dimensioni e un precedente gruppo di sviluppatori utilizzava array ovunque . Ha reso il codice molto poco flessibile. Dopo aver cambiato grossi pezzi in Liste non abbiamo notato alcuna differenza di velocità.


2
@Fortyrunner - In base alla tua esperienza, ci sono scelte del genere in Java tra astrazione e moduli di dati grezzi che fanno una differenza significativa nelle prestazioni?
euphoria83,

4
Uno dei problemi con la misurazione delle prestazioni è che devi ripetere il test costantemente contro le nuove versioni di Java. Sto lavorando a un problema nel momento in cui qualcuno ha usato un int per una chiave in una mappa (per risparmiare spazio / tempo). Ora dobbiamo cambiare tutte le linee in un nuovo oggetto: è doloroso.
Fortyrunner,

9
Quindi .. ora provo a stare lontano dai dati grezzi. Raramente fa una differenza evidente. L'hotspot è una tecnologia straordinaria e non dovresti mai provare a indovinare. Prova a scrivere un codice semplice e gestibile e Hotspot farà il resto.
Fortyrunner,

4
Ricorda che i risultati del profiler sono validi solo per la piattaforma Java su cui stai eseguendo il profiler. Quale potrebbe essere diverso dai tuoi clienti.
Mikkel Løkke,

4
Efficace Java consiglia Elenchi per il loro aiuto con l'interoperabilità delle API e anche più sicuri con la sicurezza dei tipi.
juanmf,

164

Il modo Java è che dovresti considerare quale astrazione di dati si adatta meglio alle tue esigenze. Ricorda che in Java un elenco è un tipo di dati astratto, non concreto. È necessario dichiarare le stringhe come un elenco e quindi inizializzarle utilizzando l'implementazione ArrayList.

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

Questa separazione tra tipo di dati astratto e implementazione specifica è uno degli aspetti chiave della programmazione orientata agli oggetti.

Una ArrayList implementa il tipo di dati astratto elenco utilizzando una matrice come implementazione sottostante. La velocità di accesso è praticamente identica a un array, con l'ulteriore vantaggio di poter aggiungere e sottrarre elementi a un Elenco (sebbene si tratti di un'operazione O (n) con un ArrayList) e che se si decide di cambiare l'implementazione sottostante in un secondo momento Puoi. Ad esempio, se ti rendi conto che hai bisogno dell'accesso sincronizzato, puoi cambiare l'implementazione in un Vector senza riscrivere tutto il tuo codice.

In effetti, ArrayList è stato appositamente progettato per sostituire il costrutto di array di basso livello nella maggior parte dei contesti. Se Java fosse stato progettato oggi, è del tutto possibile che gli array sarebbero stati del tutto esclusi a favore del costrutto ArrayList.

Dato che gli array mantengono tutti i dati in un pezzo contiguo di memoria (a differenza degli elenchi), l'uso di un array per archiviare migliaia di stringhe causerebbe problemi?

In Java, tutte le raccolte memorizzano solo riferimenti ad oggetti, non agli oggetti stessi. Entrambi gli array e ArrayList memorizzeranno alcune migliaia di riferimenti in un array contiguo, quindi sono essenzialmente identici. Puoi considerare che un blocco contiguo di alcune migliaia di riferimenti a 32 bit sarà sempre prontamente disponibile su hardware moderno. Questo non garantisce che non esaurirete completamente la memoria, ovviamente, solo che il blocco contiguo dei requisiti di memoria non è difficile da filtrare.


L'aggiunta può ovviamente comportare la riallocazione dell'array di backup, quindi se le prestazioni sono importanti e le dimensioni dell'array sono note in anticipo, si dovrebbe considerare l'utilizzo di ArrayList # sureCapacity.
JesperE

6
Non paghi il costo dell'associazione dinamica qui?
Uri,

2
Immagino che l'aggiunta non sia O (n) in ArrayList, ci dovrebbe essere un effetto di ammortizzazione quando si aggiunge più di una volta, ad esempio la capacità è raddoppiata anziché aumentata di appena 1
zedoo

@zedoo Penso che intendessero aggiungere e sottrarre nel mezzo.
MalcolmOcean

"Se Java fosse stato progettato oggi, è del tutto possibile che gli array sarebbero stati del tutto esclusi a favore del costrutto ArrayList." ... dubito seriamente che questo sarebbe vero. Se fosse la riscrittura della JVM oggi, ciò che hai detto è certamente possibile. Ma con la JVM che abbiamo, gli array sono di tipo fondamentale in Java.
Scott

100

Sebbene le risposte che propongono di utilizzare ArrayList abbiano senso nella maggior parte degli scenari, la vera domanda relativa alle prestazioni relative non è stata realmente risolta.

Ci sono alcune cose che puoi fare con un array:

  • Crealo
  • impostare un oggetto
  • ottenere un oggetto
  • clonalo / copialo

Conclusione generale

Sebbene le operazioni di get e set siano leggermente più lente su un ArrayList (rispettivamente 1 e 3 nanosecondi per chiamata sul mio computer), l' uso di un ArrayList rispetto a un array ha un carico di lavoro molto ridotto per qualsiasi uso non intensivo. Vi sono tuttavia alcune cose da tenere a mente:

  • le operazioni di ridimensionamento su un elenco (quando si chiama list.add(...)) sono costose e si dovrebbe cercare di impostare la capacità iniziale a un livello adeguato quando possibile (si noti che lo stesso problema si presenta quando si utilizza un array)
  • quando si ha a che fare con le primitive, le matrici possono essere significativamente più veloci poiché consentiranno di evitare molte conversioni di boxe / unboxing
  • un'applicazione che ottiene / imposta valori solo in un ArrayList (non molto comune!) potrebbe vedere un aumento delle prestazioni di oltre il 25% passando a un array

Risultati dettagliati

Ecco i risultati che ho misurato per queste tre operazioni usando la libreria di benchmarking jmh (tempi in nanosecondi) con JDK 7 su una macchina desktop x86 standard. Si noti che ArrayList non viene mai ridimensionato nei test per assicurarsi che i risultati siano comparabili. Codice di riferimento disponibile qui .

Creazione di array / array

Ho eseguito 4 test, eseguendo le seguenti dichiarazioni:

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

Risultati (in nanosecondi per chiamata, confidenza al 95%):

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]

Conclusione: nessuna differenza evidente .

ottenere operazioni

Ho eseguito 2 test, eseguendo le seguenti dichiarazioni:

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

Risultati (in nanosecondi per chiamata, confidenza al 95%):

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

Conclusione: ottenere da un array è circa il 25% più veloce rispetto a ottenere da un ArrayList, sebbene la differenza sia solo dell'ordine di un nanosecondo.

impostare le operazioni

Ho eseguito 2 test, eseguendo le seguenti dichiarazioni:

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

Risultati (in nanosecondi per chiamata):

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

Conclusione: le operazioni impostate sugli array sono circa il 40% più veloci rispetto agli elenchi, ma, per quanto riguarda get, ogni operazione impostata richiede alcuni nanosecondi, quindi per far sì che la differenza raggiunga 1 secondo, è necessario impostare gli elementi nell'elenco / array centinaia di milioni di volte!

clone / copia

Delegati copia costruttore di ArrayList per Arrays.copyOfcosì prestazioni è identica alla copia array (copia di un array tramite clone, Arrays.copyOfo System.arrayCopy fa alcuna differenza sostanziale riguarda le prestazioni ).


1
Bella analisi. Tuttavia, rispetto al tuo commento "quando si tratta di primitive, gli array possono essere significativamente più veloci in quanto consentiranno di evitare molte conversioni di boxe / unboxing", puoi avere la tua torta e mangiarla anche, con un elenco supportato da array primitivi implementazione; ad esempio: github.com/scijava/scijava-common/blob/master/src/main/java/org/… . In realtà sono abbastanza sorpreso che una cosa del genere non sia diventata Java centrale.
ctrueden,

2
@ctrueden Sì, il commento è stato applicato all'arrayList JDK standard. trove4j è una libreria ben nota che supporta elenchi primitivi. Java 8 porta alcuni miglioramenti con diversi stream specializzati in primitive.
Assylias,

Non so come funzionano i benchmark jmh, ma tengono conto della compilazione JIT che può accadere? Le prestazioni di un'applicazione Java possono variare nel tempo man mano che JVM compila il codice.
Hoffmann,

@Hoffmann Sì - include una fase di riscaldamento che è esclusa dalla misurazione.
Assylias,

97

Dovresti preferire i tipi generici agli array. Come menzionato da altri, le matrici non sono flessibili e non hanno il potere espressivo di tipi generici. (Tuttavia supportano il controllo del tipo di runtime, ma si mescola male con tipi generici.)

Ma, come sempre, quando si ottimizza è necessario seguire sempre questi passaggi:

  • Non eseguire l'ottimizzazione fino a quando non hai una versione bella, pulita e funzionante del codice. Il passaggio a tipi generici potrebbe già essere motivato in questo passaggio.
  • Quando hai una versione che è bella e pulita, decidi se è abbastanza veloce.
  • Se non è abbastanza veloce, misura le sue prestazioni . Questo passaggio è importante per due motivi. Se non misuri, non (1) conoscerai l'impatto di eventuali ottimizzazioni apportate e (2) saprai dove ottimizzare.
  • Ottimizza la parte più calda del tuo codice.
  • Misura di nuovo. Questo è importante quanto misurare prima. Se l'ottimizzazione non ha migliorato le cose, ripristinalo . Ricorda, il codice senza l'ottimizzazione era pulito, carino e funzionante.

24

Immagino che il poster originale provenga da uno sfondo C ++ / STL che sta creando confusione. In C ++ std::listè un elenco doppiamente collegato.

In Java [java.util.]Listè un'interfaccia senza implementazione (pura classe astratta in termini di C ++). Listpuò essere un elenco doppiamente collegato - java.util.LinkedListviene fornito. Tuttavia, 99 volte su 100 quando si desidera crearne una nuova List, si desidera utilizzare java.util.ArrayListinvece, che è l'equivalente approssimativo di C ++ std::vector. Esistono altre implementazioni standard, come quelle restituite da java.util.Collections.emptyList()ejava.util.Arrays.asList() .

Dal punto di vista delle prestazioni, c'è un piccolo successo dovuto al fatto di dover passare attraverso un'interfaccia e un oggetto in più, tuttavia l'allineamento del runtime significa che raramente ha un significato. Ricorda anche che in Stringgenere sono un oggetto più un array. Quindi, per ogni voce, probabilmente hai altri due oggetti. In C ++ std::vector<std::string>, sebbene copiando per valore senza un puntatore in quanto tale, gli array di caratteri formeranno un oggetto per stringa (e questi di solito non saranno condivisi).

Se questo particolare codice è veramente sensibile alle prestazioni, è possibile creare un singolo char[]array (o anche byte[]) per tutti i caratteri di tutte le stringhe, quindi un array di offset. IIRC, ecco come viene implementato javac.


1
Grazie per la risposta. Ma no, non confondo l'elenco C ++ con l'elenco delle interfacce Java. Ho posto la domanda in questo modo perché volevo confrontare le prestazioni delle implementazioni di List come ArrayList e Vector con array grezzi.
euphoria83

Sia ArrayList che Vector "mantengono tutti i dati in una porzione contigua di memoria".
Tom Hawtin - tackline

13

Concordo sul fatto che nella maggior parte dei casi è necessario scegliere la flessibilità e l'eleganza di ArrayList rispetto agli array, e nella maggior parte dei casi l'impatto sulle prestazioni del programma sarà trascurabile.

Tuttavia, se stai eseguendo iterazioni costanti e pesanti con poche modifiche strutturali (nessuna aggiunta o rimozione) per, ad esempio, il rendering della grafica del software o una macchina virtuale personalizzata, i miei test di benchmarking ad accesso sequenziale mostrano che gli ArrayLists sono 1,5 volte più lenti degli array sul mio sistema (Java 1.6 sul mio iMac di un anno).

Qualche codice:

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

Ho trovato questa una risposta interessante, ma mi chiedo se è ancora peggio se ArrayList non è inizializzato con una dimensione iniziale in memoria. Generalmente il vantaggio di usare ArrayList su un array nativo in un certo senso è che non lo saprai e non dovrai preoccuparti. Le liste di array sono create per impostazione predefinita con una lunghezza iniziale 10 e quindi ridimensionate. Penso che il ridimensionamento sia costoso. Non ho provato a confrontarlo ovviamente.
Zak Patterson,

4
Questo micro benchmark ha dei difetti (nessun riscaldamento, operazioni non in un metodo separato, quindi la parte dell'arraylist non è mai ottimizzata da JIT ecc.)
assylias

Sono d'accordo con Assylias. I risultati di questo benchmark non dovrebbero essere attendibili.
Stephen C,

@StephenC Ho aggiunto un micro benchmark corretto (che mostra che le operazioni di get sono comparabili).
assylias,

11

Bene, in primo luogo vale la pena chiarire intendi "elenco" nel senso classico delle strutture di dati della scienza (ovvero un elenco collegato) o intendi java.util.List? Se intendi un java.util.List, è un'interfaccia. Se vuoi usare un array, usa l'implementazione di ArrayList e otterrai un comportamento e una semantica simili a quelli di un array. Problema risolto.

Se intendi un array rispetto a un elenco collegato, è un argomento leggermente diverso per il quale torniamo a Big O (ecco una semplice spiegazione inglese se questo è un termine sconosciuto.

Vettore;

  • Accesso casuale: O (1);
  • Inserisci: O (n);
  • Elimina: O (n).

Lista collegata:

  • Accesso casuale: O (n);
  • Inserisci: O (1);
  • Elimina: O (1).

Quindi scegli quello che meglio si adatta al modo in cui ridimensioni l'array. Se ridimensioni, inserisci ed elimina molto, forse un elenco collegato è una scelta migliore. Lo stesso vale se l'accesso casuale è raro. Lei menziona l'accesso seriale. Se stai principalmente effettuando l'accesso seriale con pochissime modifiche, probabilmente non importa quale scegli.

Gli elenchi collegati hanno un overhead leggermente più elevato poiché, come dici tu, hai a che fare con blocchi di memoria potenzialmente non contigui e (effettivamente) puntatori all'elemento successivo. Questo probabilmente non è un fattore importante a meno che tu non abbia a che fare con milioni di voci.


intendo java.util.List interface
euphoria83

1
L'accesso casuale O (n) sulla lista collegata mi sembra un grosso problema.
Bjorn,

11

Ho scritto un piccolo punto di riferimento per confrontare ArrayLists con Arrays. Sul mio vecchio laptop, il tempo di attraversare 1000 volte un arraylist di 5000 elementi è stato di circa 10 millisecondi più lento del codice array equivalente.

Quindi, se non stai facendo altro che iterare l'elenco e lo stai facendo molto, allora forse vale l'ottimizzazione. In caso contrario, mi piacerebbe utilizzare l'elenco, perché ce la farà più facile quando si fa necessità di ottimizzare il codice.

nb io fatto notato che usando for String s: stringsListera di circa il 50% più lento rispetto all'utilizzo di un vecchio-stile per-loop per accedere all'elenco. Vai a capire ... Ecco le due funzioni che ho cronometrato; l'array e l'elenco sono stati riempiti con 5000 stringhe casuali (diverse).

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: ottimo lavoro! Quali sono i tempi di esecuzione effettivi per entrambi? Puoi dirmi la dimensione delle stringhe che stavi usando? Inoltre, poiché l'uso di "String s: stringsList" ha richiesto più tempo, questa è la mia paura principale nell'usare le astrazioni più elevate in Java in generale.
euphoria83

Non importa davvero quanto siano lunghe le stringhe per questo mcirobenchmark. Non c'è gc e char[]non viene toccato (questo non è C).
Tom Hawtin - tackline

I tempi tipici per me sono stati ~ 25ms per la versione array, ~ 35ms per la versione ArrayList. Le stringhe erano lunghe 15-20 caratteri. Come dice Tom, la dimensione della stringa non fa molta differenza, con una stringa di ~ 100 caratteri i tempi erano quasi uguali.
Chris,

3
Come hai misurato? La misurazione ingenua nei micro benchmark Java di solito genera più disinformazione delle informazioni. Attenzione alle affermazioni di cui sopra.
jmg,

6

No, poiché tecnicamente l'array memorizza solo il riferimento alle stringhe. Le stringhe stesse vengono allocate in una posizione diversa. Per mille articoli, direi che un elenco sarebbe meglio, è più lento, ma offre maggiore flessibilità ed è più facile da usare, soprattutto se si intende ridimensionarli.


5
L'elenco contiene anche solo riferimenti alle stringhe.
Peter Štibraný,

6

Se ne hai migliaia, considera l'utilizzo di un trie. Un trie è una struttura ad albero che unisce i prefissi comuni della stringa memorizzata.

Ad esempio, se le stringhe fossero

intern
international
internationalize
internet
internets

Il trie memorizzava:

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

Le stringhe richiedono 57 caratteri (incluso il terminatore null, '\ 0') per l'archiviazione, oltre a qualsiasi dimensione dell'oggetto String che li contiene. (In verità, dovremmo probabilmente arrotondare tutte le dimensioni fino a multipli di 16, ma ...) Chiamalo 57 + 5 = 62 byte, approssimativamente.

Il trie richiede 29 (incluso il terminatore null, '\ 0') per l'archiviazione, oltre alla dimensione dei nodi trie, che sono un riferimento a un array e un elenco di nodi trie figlio.

Per questo esempio, probabilmente ne viene fuori lo stesso; per migliaia, probabilmente esce meno fintanto che hai prefissi comuni.

Ora, quando usi il trie in un altro codice, dovrai convertirlo in String, usando probabilmente StringBuffer come intermediario. Se molte delle stringhe vengono utilizzate contemporaneamente come stringhe, al di fuori del trie, è una perdita.

Ma se ne usi solo alcuni in quel momento - diciamo, per cercare cose in un dizionario - il trie può farti risparmiare molto spazio. Sicuramente meno spazio rispetto alla loro memorizzazione in un HashSet.

Dici di accedervi "in serie" - se ciò significa in ordine alfabetico in sequenza, il trie ovviamente ti dà anche l'ordine alfabetico gratuitamente, se lo esegui prima in profondità.


1
trie è come una libreria o come la creo?
euphoria83,

Un trie sarebbe utile solo nel caso di stringhe tokenizzate, non se qualcuno sta memorizzando il testo in esecuzione come stringhe.
MN,

5

AGGIORNARE:

Come notato da Mark, non vi è alcuna differenza significativa dopo il riscaldamento della JVM (diversi passaggi di test). Controllato con matrice ricreata o anche con un nuovo passaggio che inizia con una nuova riga di matrice. Con grande probabilità questo segno semplice array con accesso all'indice non deve essere utilizzato a favore delle raccolte.

Ancora i primi 1-2 passaggi dell'array semplice sono 2-3 volte più veloci.

POSTO ORIGINALE:

Troppe parole per l'argomento troppo semplici da controllare. L'array è senza dubbio più volte più veloce di qualsiasi contenitore di classe . Corro su questa domanda alla ricerca di alternative per la mia sezione critica delle prestazioni. Ecco il codice prototipo che ho creato per verificare la situazione reale:

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

Ed ecco la risposta:

Basato sull'array (la linea 16 è attiva):

Time: 7064

In base all'elenco (la riga 17 è attiva):

Time: 20950

Qualche altro commento su "più veloce"? Questo è abbastanza capito. La domanda è quando circa 3 volte più veloce è meglio per te della flessibilità di Elenco. Ma questa è un'altra domanda. A proposito ho controllato anche questo sulla base di costruito manualmente ArrayList. Quasi lo stesso risultato.


2
3volte più veloce vero, ma insignificante. 14msnon è molto tempo
0x6C38

1
Il benchmark non sta prendendo in considerazione il riscaldamento di JVM. Cambia main () in test () e chiama ripetutamente test da main. Alla terza o quarta prova, viene eseguito molte volte più velocemente. A quel punto, vedo che l'array è circa 9 volte più veloce dell'array.
Mike,

5

Dato che ci sono già molte buone risposte qui, vorrei darvi alcune altre informazioni di vista pratica, che è il confronto delle prestazioni di inserimento e iterazione: array primitivo vs elenco collegato in Java.

Questo è un semplice controllo delle prestazioni.
Pertanto, il risultato dipenderà dalle prestazioni della macchina.

Il codice sorgente utilizzato per questo è di seguito:

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

Il risultato della prestazione è di seguito:

inserisci qui la descrizione dell'immagine


4

la lista è più lenta delle matrici. Se hai bisogno di efficienza usa le matrici. Se hai bisogno di flessibilità usa la lista.


4

Ricorda che un ArrayList incapsula un array, quindi c'è poca differenza rispetto all'utilizzo di un array primitivo (tranne per il fatto che un elenco è molto più semplice con cui lavorare in java).

L'unica volta in cui ha senso preferire un array a un ArrayList è quando si memorizzano primitive, ovvero byte, int, ecc. Ed è necessaria la particolare efficienza dello spazio che si ottiene utilizzando array primitivi.


4

La scelta dell'array rispetto all'elenco non è così importante (considerando le prestazioni) nel caso di memorizzazione di oggetti stringa. Poiché sia ​​l'array che l'elenco memorizzeranno i riferimenti agli oggetti stringa, non gli oggetti reali.

  1. Se il numero di stringhe è quasi costante, utilizzare un array (o ArrayList). Ma se il numero varia troppo, è meglio usare LinkedList.
  2. Se c'è (o ci sarà) la necessità di aggiungere o eliminare elementi nel mezzo, allora devi certamente usare LinkedList.

4

Sono venuto qui per avere un'idea migliore dell'impatto sulle prestazioni dell'uso degli elenchi sugli array. Ho dovuto adattare il codice qui per il mio scenario: array / list di ~ 1000 ints usando principalmente getter, ovvero array [j] vs. list.get (j)

Prendendo il meglio di 7 per non essere scientifico al riguardo (i primi con un elenco di 2,5 volte più lento) ottengo questo:

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

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

- quindi, circa il 30% più veloce con l'array

Il secondo motivo per la pubblicazione ora è che nessuno menziona l'impatto se si esegue un codice di matematica / matrice / simulazione / ottimizzazione con loop nidificati .

Supponi di avere tre livelli nidificati e che il ciclo interno sia due volte più lento rispetto a 8 volte il colpo di prestazione. Qualcosa che sarebbe successo in un giorno ora richiede una settimana.

* EDIT Abbastanza scioccato qui, per i calci ho provato a dichiarare int [1000] anziché Integer [1000]

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

L'uso di Integer [] vs. int [] rappresenta un doppio successo prestazionale, ListArray con iteratore è 3 volte più lento di int []. Pensavo davvero che le implementazioni dell'elenco Java fossero simili agli array nativi ...

Codice di riferimento (chiamare più volte):

    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

Se si conosce in anticipo la dimensione dei dati, un array sarà più veloce.

Una lista è più flessibile. È possibile utilizzare un ArrayList supportato da un array.


ArrayList ha un metodo sureCapacity () che prealloca l'array di supporto alla dimensione specificata.
JesperE

Oppure puoi specificare la dimensione al momento della costruzione. Anche "più veloce" qui significa "pochi microsecondi per allocare due aree di memoria anziché una"
Aaron Digulla

3

Puoi vivere con una dimensione fissa, le matrici saranno più veloci e avranno bisogno di meno memoria.

Se è necessaria la flessibilità dell'interfaccia Elenco con l'aggiunta e la rimozione di elementi, rimane la domanda su quale implementazione scegliere. Spesso ArrayList è raccomandato e utilizzato in ogni caso, ma anche ArrayList ha i suoi problemi di prestazioni se gli elementi all'inizio o al centro dell'elenco devono essere rimossi o inseriti.

Potresti quindi dare un'occhiata a http://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list che introduce GapList. Questa nuova implementazione di elenchi combina i punti di forza di ArrayList e LinkedList ottenendo prestazioni molto buone per quasi tutte le operazioni.


2

A seconda dell'implementazione. è possibile che una matrice di tipi primitivi sia più piccola e più efficiente di ArrayList. Questo perché l'array memorizzerà i valori direttamente in un blocco contiguo di memoria, mentre l'implementazione ArrayList più semplice memorizzerà i puntatori a ciascun valore. Soprattutto su una piattaforma a 64 bit, questo può fare una grande differenza.

Naturalmente, è possibile che l'implementazione di jvm abbia un caso speciale per questa situazione, nel qual caso le prestazioni saranno le stesse.


2

Elenco è il modo preferito in Java 1.5 e oltre in quanto può utilizzare generici. Le matrici non possono avere generici. Anche le matrici hanno una lunghezza predefinita, che non può crescere dinamicamente. L'inizializzazione di un array di grandi dimensioni non è una buona idea. ArrayList è il modo di dichiarare un array con generici e può crescere dinamicamente. Ma se elimina e inserisci viene utilizzata più frequentemente, l'elenco collegato è la struttura di dati più veloce da utilizzare.


2

Le matrici consigliate ovunque si possono usare al posto dell'elenco, specialmente nel caso in cui si sappia che il conteggio e la dimensione degli articoli non cambierebbe.

Vedi le migliori pratiche Oracle Java: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

Naturalmente, se hai bisogno di aggiungere e rimuovere oggetti dalla raccolta molte volte elenchi di facile utilizzo.


La documentazione a cui si è collegati ha più di 10 anni, vale a dire vale per java 1.3. Da allora sono stati apportati importanti miglioramenti alle prestazioni ...
assylias,

@assylias vede le risposte sopra, contiene test delle prestazioni, che dicono che gli array sono più veloci
Nik

3
So di averne scritto uno. Ma non credo che "gli array siano raccomandati ovunque sia possibile utilizzarli al posto degli elenchi " è un buon consiglio. ArrayList dovrebbe essere la scelta predefinita nella maggior parte delle situazioni a meno che non si tratti di primitive e il codice sia sensibile alle prestazioni.
Assylias,

2

Nessuna delle risposte aveva informazioni che mi interessavano: scansione ripetitiva della stessa matrice molte volte. Ho dovuto creare un test JMH per questo.

Risultati (Java 1.8.0_66 x32, iterando l'array normale è almeno 5 volte più veloce di ArrayList):

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

Test

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

"Migliaia" non è un numero elevato. Alcune migliaia di stringhe di lunghezza di paragrafo sono nell'ordine di un paio di megabyte. Se tutto ciò che vuoi fare è accedere a questi in serie, usa un Elenco immutabile collegato singolarmente .


8 byte sulla maggior parte delle implementazioni a 64 bit.
Tom Hawtin - tackline

C'è qualche prova che questa cosa sia più veloce di java.util.LinkedList? Che è anche "in memoria"? Può anche essere reso immutabile, come se ciò potesse fare la differenza.
Marchese di Lorne,

1

Non entrare nella trappola dell'ottimizzazione senza un adeguato benchmarking. Come altri hanno suggerito di usare un profiler prima di fare qualsiasi ipotesi.

Le diverse strutture dati che hai elencato hanno scopi diversi. Un elenco è molto efficace nell'inserimento di elementi all'inizio e alla fine, ma soffre molto quando si accede a elementi casuali. Un array ha una memoria fissa ma fornisce un accesso casuale veloce. Finalmente un ArrayList migliora l'interfaccia con un array permettendogli di crescere. Normalmente la struttura dei dati da utilizzare dovrebbe essere dettata dalla modalità di accesso o aggiunta dei dati memorizzati.

Informazioni sul consumo di memoria. Sembra che tu stia mescolando alcune cose. Un array ti darà solo un pezzo continuo di memoria per il tipo di dati che hai. Non dimenticare che java ha un tipo di dati fisso: booleano, char, int, long, float e Object (questo include tutti gli oggetti, anche un array è un oggetto). Significa che se dichiari un array di stringhe [1000] o MyObject myObjects [1000] ottieni solo 1000 caselle di memoria abbastanza grandi da memorizzare la posizione (riferimenti o puntatori) degli oggetti. Non hai 1000 scatole di memoria abbastanza grandi da adattarsi alle dimensioni degli oggetti. Non dimenticare che i tuoi oggetti vengono prima creati con "nuovo". Questo è quando l'allocazione della memoria viene eseguita e successivamente un riferimento (il loro indirizzo di memoria) viene memorizzato nella matrice. L'oggetto non viene copiato nell'array ma è solo un riferimento.


1

Non penso che faccia davvero la differenza per gli archi. Ciò che è contiguo in una matrice di stringhe sono i riferimenti alle stringhe, le stringhe stesse sono memorizzate in punti casuali nella memoria.

Le matrici contro le liste possono fare la differenza per i tipi primitivi, non per gli oggetti. Se conosci in anticipo il numero di elementi e non hai bisogno di flessibilità, un array di milioni di numeri interi o doppi sarà più efficiente in memoria e leggermente più veloce di un elenco, perché in effetti verranno memorizzati contigui e accessibili istantaneamente. Ecco perché Java utilizza ancora matrici di caratteri per stringhe, matrici di ints per dati immagine, ecc.


1

L'array è più veloce: tutta la memoria è pre-allocata in anticipo.


1

Molti microbench mark qui riportati hanno trovato numeri di pochi nanosecondi per cose come le letture array / ArrayList. Questo è abbastanza ragionevole se tutto è nella cache L1.

Una cache di livello superiore o l'accesso alla memoria principale può avere tempi di grandezza dell'ordine di 10nS-100nS, rispetto a 1nS per cache L1. L'accesso a una ArrayList ha un ulteriore riferimento indiretto alla memoria, e in una vera applicazione potresti pagare questo costo quasi mai quasi mai, a seconda di cosa sta facendo il tuo codice tra gli accessi. E, naturalmente, se hai un sacco di piccole liste di array, questo potrebbe aumentare l'utilizzo della memoria e aumentare la probabilità che tu abbia problemi di cache.

Il poster originale sembra utilizzarne solo uno e accedere a molti contenuti in breve tempo, quindi non dovrebbe essere una grande difficoltà. Ma potrebbe essere diverso per le altre persone e dovresti stare attento quando interpreti i microbenchmark.

Le stringhe Java, tuttavia, sono terribilmente dispendiose, specialmente se ne memorizzi molte di piccole dimensioni (guardale con un analizzatore di memoria, sembra essere> 60 byte per una stringa di pochi caratteri). Un array di stringhe ha un'indirizzamento indiretto all'oggetto String e un altro dall'oggetto String a un char [] che contiene la stringa stessa. Se qualcosa sta per far saltare la cache L1, è questo, combinato con migliaia o decine di migliaia di stringhe. Quindi, se sei serio - davvero serio - a ritagliare quante più prestazioni possibili, allora potresti guardare a farlo diversamente. Potresti, per esempio, contenere due array, un carattere [] con tutte le stringhe al suo interno, uno dopo l'altro, e un int [] con offset all'inizio. Questo sarà un PITA con cui fare qualsiasi cosa, e quasi sicuramente non ne avrai bisogno. E se lo fai, tu '


0

Dipende da come devi accedervi.

Dopo l'archiviazione, se si desidera principalmente eseguire un'operazione di ricerca, con inserimenti / eliminazioni insufficienti o assenti, selezionare Array (poiché la ricerca viene eseguita in O (1) negli array, mentre l'aggiunta / eliminazione potrebbe richiedere il riordino degli elementi) .

Dopo la memorizzazione, se lo scopo principale è aggiungere / eliminare stringhe, con un'operazione di ricerca minima o nulla, selezionare Elenco.


0

ArrayList utilizza internamente un oggetto array per aggiungere (o memorizzare) gli elementi. In altre parole, ArrayList è supportato dalla struttura di dati Array. L'array di ArrayList è ridimensionabile (o dinamico).

L'array è più veloce dell'array perché ArrayList utilizza internamente l'array. se possiamo aggiungere direttamente elementi in Array e aggiungere indirettamente elementi in Array tramite ArrayList, sempre direttamente il meccanismo è più veloce del meccanismo indiretto.

Esistono due metodi add () sovraccarichi nella classe ArrayList:
1 add(Object) .: aggiunge oggetto alla fine dell'elenco.
2 add(int index , Object ) .: inserisce l'oggetto specificato nella posizione specificata nell'elenco.

Come cresce la dimensione di ArrayList in modo dinamico?

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

Un punto importante da notare dal codice sopra è che stiamo verificando la capacità di ArrayList, prima di aggiungere l'elemento. sureCapacity () determina qual è la dimensione corrente degli elementi occupati e qual è la dimensione massima dell'array. Se la dimensione degli elementi riempiti (incluso il nuovo elemento da aggiungere alla classe ArrayList) è maggiore della dimensione massima dell'array, aumentare la dimensione dell'array. Ma la dimensione dell'array non può essere aumentata dinamicamente. Quindi ciò che accade internamente è il nuovo array creato con capacità

Fino a Java 6

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

(Aggiornamento) Da Java 7

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

inoltre, i dati dal vecchio array vengono copiati nel nuovo array.

Avere metodi generali in ArrayList è per questo che Array è più veloce di ArrayList.


0

Matrici - Sarebbe sempre meglio quando dovessimo ottenere un recupero più rapido dei risultati

Elenchi: esegue risultati sull'inserimento e l'eliminazione poiché possono essere eseguiti in O (1) e ciò fornisce anche metodi per aggiungere, recuperare ed eliminare facilmente i dati. Molto più facile da usare.

Ma ricorda sempre che il recupero dei dati sarebbe rapido quando la posizione dell'indice nell'array in cui sono memorizzati i dati - è nota.

Ciò potrebbe essere ottenuto ordinando l'array. Quindi questo aumenta il tempo necessario per recuperare i dati (ad es., Memorizzazione dei dati + ordinamento dei dati + ricerca della posizione in cui i dati vengono trovati). Quindi questo aumenta la latenza aggiuntiva per recuperare i dati dall'array anche se possono essere bravi a recuperare i dati prima.

Quindi questo potrebbe essere risolto con la struttura dei dati trie o la struttura dei dati ternari. Come discusso in precedenza, la struttura dei dati di trie sarebbe molto efficiente nella ricerca dei dati, la ricerca di una parola particolare può essere effettuata in grandezza O (1). Quando il tempo conta, ad es. se devi cercare e recuperare rapidamente i dati, puoi utilizzare la struttura dei dati trie.

Se vuoi che il tuo spazio di memoria venga consumato meno e desideri prestazioni migliori, vai con la struttura dei dati ternaria. Entrambi sono adatti per memorizzare un numero enorme di stringhe (ad es. Come le parole contenute nel dizionario).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.