C'è mai un motivo per usare un array quando sono disponibili gli elenchi? [chiuso]


30

Sembra che List<T>in C # possa fare tutto ciò che un array può fare e altro ancora, e sembra altrettanto efficiente in termini di memoria e prestazioni di un array.

Quindi perché dovrei mai voler usare un array?

Ovviamente non sto chiedendo dei casi in cui un'API o altri vincoli esterni (ad esempio la funzione Main) mi richiedono di utilizzare un array ... Sto solo chiedendo di creare nuove strutture di dati nel mio codice.


56
Per implementareList<T>
maniaco del cricchetto,

43
is also just as efficient in memory and performance as an array- um. Da dove hai preso questa idea?
Oded,

12
Molteplici dimensioni come var test = new string[5,5];)
Knerd

7
C'è anche l'array byte [] comunemente usato.
SBoss,

11
In particolare per C #, Eric Lippert ha scritto di questo in blogs.msdn.com/b/ericlippert/archive/2008/09/22/… .
Mephy,

Risposte:


26

Lo stesso motivo per cui non guido un camion quando vado al lavoro. Non uso qualcosa di cui non userò le funzionalità.

Prima di tutto un array è un costrutto primitivo, quindi un array è sicuramente più veloce ed efficiente di un List <>, quindi il tuo argomento non è vero. L'array è anche disponibile ovunque e noto agli sviluppatori che utilizzano lingue e piattaforme diverse.

Il motivo più importante per cui utilizzo un array anziché un Elenco <> è implicare che i dati siano a lunghezza fissa . Se non aggiungerò o rimuoverò alcun elemento da quella raccolta di dati, voglio assicurarmi che il tipo lo rifletta.

Un'altra cosa è supponiamo che tu stia implementando una nuova struttura di dati e hai letto alcuni articoli a riguardo. Ora, durante l'implementazione di algoritmi specifici, non puoi sempre fare affidamento sull'implementazione di qualcun altro di un tipo che è di uso generale. Passa da .NET a Mono e anche tra diverse versioni del framework.

Ed a volte è più facile portare un pezzo di codice che utilizza un array anziché un tipo dipendente dal framework.


4
In che modo un array è "più veloce" di un elenco, considerando che List<T>viene implementato utilizzando un array? Se si conosce in anticipo il conteggio degli elementi (che è necessario sapere quando si utilizza un array), è possibile utilizzare tale conoscenza anche durante l'inizializzazione dell'elenco.
Theodoros Chatzigiannakis,

1
@TheodorosChatzigiannakis Quando si utilizza un array, allochiamo la memoria e facciamo semplicemente assegnazioni, semplicemente malloc (), nessun sovraccarico. Quando si utilizza Elenco <>, si "aggiungono" elementi e quando si aggiungono elementi Elenco <> si effettua un controllo dei limiti e si chiama il metodo AssicurCapacity che copierà il contenuto su un altro array appena allocato se la capacità corrente non è sufficiente. Significa che se non è necessario allocare dinamicamente nuova memoria, le prestazioni di List non sono buone come quelle dell'array.
Mert Akcakaya,

4
Quello che stai dicendo è corretto, ma si presume che sia non conosciamo il numero di elemento in anticipo (nel qual caso, gli array non sono utili in ogni caso), o che ci fanno conoscere il numero di elemento in anticipo, ma abbiamo volutamente non uso nel caso dell'elenco. Se noi facciamo usare il conteggio elemento per inizializzare la lista con la capacità desiderata, si ottiene solo un'allocazione array (fino a raggiungere tale capacità), quindi paghiamo gli stessi costi delle prestazioni. Tutte le altre operazioni comuni sono uguali (ricerca, recupero per indice, assegnazione per indice), ad eccezione di ulteriori aggiunte (che gli array non hanno affatto).
Theodoros Chatzigiannakis,

3
@TheodorosChatzigiannakis: le matrici sono più veloci delle liste. Basta eseguire un semplice benchmark per verificare.
Mehrdad,

1
@TheodorosChatzigiannakis, Sì, ma ci sono ancora controlli al contorno.
Mert Akcakaya,

18

Hai bisogno di array per gestire la tua collezione di strutture mutabili , ovviamente, e cosa faremmo senza quelli.

struct EvilMutableStruct { public double X; } // don't do this

EvilMutableStruct[] myArray = new EvilMutableStruct[1];
myArray[0] = new EvilMutableStruct()
myArray[0].X = 1; // works, this modifies the original struct

List<EvilMutableStruct> myList = new List<EvilMutableStruct>();
myList.Add(new EvilMutableStruct());
myList[0].X = 1; // does not work, the List will return a *copy* of the struct

(si noti che potrebbero esserci alcuni casi in cui è desiderabile una serie di strutture mutabili, ma di solito questo comportamento diverso delle strutture mutabili all'interno di array rispetto ad altre raccolte è una fonte di errori che dovrebbero essere evitati)


Più seriamente, è necessario un array se si desidera passare un elemento per riferimento . vale a dire

Interlocked.Increment(ref myArray[i]);  // works
Interlocked.Increment(ref myList[i]);   // does not work, you can't pass a property by reference

Ciò può essere utile per un codice thread-safe senza blocco.


È necessario un array se si desidera inizializzare in modo rapido ed efficiente la raccolta di dimensioni fisse con il valore predefinito .

double[] myArray = new double[1000]; // contains 1000 '0' values
                                     // without further initialisation

List<double> myList = new List<double>(1000) // internally contains 1000 '0' values, 
                                             // since List uses an array as backing storage, 
                                             // but you cannot access those
for (int i =0; i<1000; i++) myList.Add(0);   // slow and inelegant

(nota che sarebbe possibile implementare un costruttore per List che faccia lo stesso, è solo che c # non offre questa funzione)


è necessario un array se si desidera copiare in modo efficiente parti della raccolta

Array.Copy(array1, index1, array2, index2, length) // can't get any faster than this

double[,] array2d = new double[10,100];
double[] arraySerialized = new double[10*100];
Array.Copy(array2d, 0, arraySerialized, 0, arraySerialized.Length);
// even works for different dimensions

(di nuovo, questo è qualcosa che potrebbe essere implementato anche per List, ma questa funzione non esiste in c #)


Se si suppone che un tipo rappresenti un gruppo di variabili correlate ma indipendenti bloccate insieme al nastro adesivo (ad esempio le coordinate di un punto), una struttura a campo esposto è la rappresentazione migliore. Se una variabile deve contenere un gruppo di tali gruppi, un array di quel tipo di struttura è la rappresentazione migliore. Non si dovrebbero usare strutture mutabili in luoghi in cui si desidera un oggetto, ma ciò non significa che si dovrebbero usare oggetti o cose che fingono di essere oggetti in luoghi in cui si desidera un mucchio di variabili incollate insieme al nastro adesivo.
supercat

Ci sono alcune situazioni in cui potresti desiderare una struttura mutabile, come quando hai bisogno dell'ultimo bit di performance e non puoi permetterti allocazioni e riferimenti di heap, e in quel caso una serie di strutture è la soluzione migliore. Buon saggio qui . Ma non sarei d'accordo sul fatto che una classe non dovrebbe rappresentare "un mucchio di variabili messe insieme". Le strutture e le classi concettuali sono per lo più le stesse, le differenze sono principalmente dovute ai dettagli di implementazione.
HugoRune,

Se si desidera utilizzare una raccolta di riferimenti a oggetti mutabili come raccolta di gruppi indipendenti di variabili indipendenti, è necessario stabilire e mantenere un invariante che ciascun elemento identifica un oggetto a cui non esistono altri riferimenti non effimeri in qualsiasi parte dell'universo. Questo può certamente essere fatto (e spesso lo è), ma non c'è nulla nella lingua o nella struttura per aiutare il programmatore a sostenere quell'invariante. Al contrario, un array di lunghezza 100 di un tipo di struttura con quattro campi di istanza incapsulerà automaticamente e permanentemente 400 variabili indipendenti.
supercat

Penso che questi commenti siano il posto sbagliato per discutere ulteriormente di questo problema e, visto che hai aggiunto la tua risposta , non sembra che sia necessario duplicare queste informazioni qui. Basti dire che le matrici possono essere utilizzate per gestire strutture mutabili. Se e quando utilizzare il comportamento di implementazione di tali strutture per imporre determinati vincoli di dati va oltre la portata della mia risposta.
HugoRune,

15

Quindi perché dovrei mai voler usare un array?

Raramente , avrai uno scenario in cui sai che hai bisogno di un numero fisso di elementi. Dal punto di vista del design, questo dovrebbe essere evitato. Se hai bisogno di 3 cose, la natura del business significa che molto spesso ne avrai bisogno 4 nella prossima versione.

Tuttavia, quando si verifica effettivamente questo raro scenario, è utile utilizzare un array per imporre l'invariante a dimensione fissa. Fornisce un segnale agli altri programmatori che ha una dimensione fissa e aiuta a prevenire l'uso improprio laddove qualcuno aggiunge o rimuove un elemento, infrangendo le aspettative altrove nel codice.


23
La dimensione delle matrici non è impostata su pietra nel momento in cui si scrive il codice. Può, e spesso è, una variabile di runtime. Non cambia mai dopo che l'array è stato creato .

11
Non è insolito gestire un numero fisso di elementi. Se lavori con punti 3D, probabilmente in futuro non lavorerai con punti 5D. Il problema più grande con le matrici è che sono mutabili e non hanno etichette comode attaccate ai loro elementi.
Doval,

3
@JanDvorak Le liste possono ridursi o crescere e quindi non ha senso in nessun contesto che coinvolge un numero fisso di elementi.
Doval,

9
Raramente? Non tutti lavorano su mostruosità aziendali ultra-configurabili super complicate.
whatsisname

3
È molto comune avere un numero fisso di elementi. Assicurati solo che la lunghezza sia definita da una costante, quindi quando la prossima versione aumenta il numero di elementi da 3 a 4, cambi semplicemente la costante e tutto ciò che si basa su questo viene adattato.
Hans,

9

In realtà la tua domanda ha già ricevuto risposta in precedenza .

e sembra altrettanto efficiente nella memoria e nelle prestazioni di un array.

Non lo è. Dalla domanda che ho collegato:

List/foreach:  3054ms (589725196)
Array/foreach: 1860ms (589725196)

Le matrici sono due volte più veloci in alcuni casi importanti. Sono certo che anche l'utilizzo della memoria differisca in modo non banale.

Dal momento che la premessa principale della tua domanda viene così sconfitta, suppongo che questo risponda alla tua domanda. Inoltre, a volte gli array sono obbligati dall'API Win32, dallo shader della GPU o da altre librerie non DotNet.

Anche all'interno di DotNet, alcuni metodi consumano e / o restituiscono array (come String.Split). Ciò significa che ora è necessario consumare il costo della chiamata ToListe ToArraycontinuamente, oppure è necessario conformarsi e utilizzare l'array, eventualmente continuando il ciclo propagando questo agli utenti a valle poveri del proprio codice.

Altre domande e risposte su Stack Overflow su questo argomento:


È interessante notare, che i punti di risposta che se si utilizzano cicli indicizzati vecchio stile ( per posto di foreach ) la differenza di prestazioni è minimo.
user949300

3

Oltre ai motivi elencati in altre risposte, l'array letterale impiega meno caratteri per dichiarare:

var array = new [] { "A", "B", "C" };
var list = new List<string>() { "A", "B", "C" };

L'uso di array invece di Listrende il codice un po 'più breve e un po' più leggibile nei casi in cui (1) è necessario passare qualsiasi valore IEnumerable<T>letterale o (2) in cui altre funzionalità diList non contano e è necessario utilizzare un tipo di elenco letterale.

L'ho fatto occasionalmente in unit test.


1
Sì, solo per aggiungere. Funziona anche di te è necessario passare un IList <T>
Esben Skov Pedersen

+1, spesso ho qualche operazione per un paio di oggetti dello stesso tipo che non sono in una raccolta. È facile, breve e chiaro scrivere foreach( var x in new []{ a, b, c ) ) DoStuff( x )o new []{ a, b, c ).Select( ... )ecc.
stijn

3

Questo è rigorosamente dal punto di vista OO.

Mentre non riesco a pensare a un motivo per passare solo un array in giro, posso certamente vedere situazioni in cui una rappresentazione di array interna alla classe è probabilmente la scelta migliore.

Mentre ci sono altre opzioni che offrono caratteristiche simili, nessuna sembra intuitiva come una matrice per problemi di gestione delle permutazioni, nidificata per loop, rappresentazione di matrici, bitmap e algoritmi di interfogliatura dei dati.

Esiste un numero considerevole di campi scientifici che dipendono ampiamente dalla matematica matriciale. (ad es. elaborazione di immagini, correzione di errori di dati, elaborazione di segnali digitali, una serie di problemi matematici applicati). La maggior parte degli algoritmi in questi campi sono scritti in termini di utilizzo di matrici / matrici multidimensionali. Quindi sarebbe più naturale implementare gli algoritmi così come sono definiti piuttosto che renderli più "software" amichevoli a scapito di perdere i collegamenti diretti ai documenti su cui si basano gli algoritmi.

Come ho detto, in questi casi probabilmente puoi cavartela usando le liste ma questo aggiunge un ulteriore livello di complessità oltre a quelli che sono già algoritmi complessi.


3

Questo in realtà vale per altre lingue che hanno anche elenchi (come Java o Visual Basic). Esistono casi in cui è necessario utilizzare un array perché un metodo restituisce un array anziché un elenco.

In un vero programma, non penso che un array verrà usato molto spesso, ma a volte sai che i dati avranno dimensioni fisse e ti piace il piccolo guadagno in termini di prestazioni ottenuto dall'uso di un array. La micro-ottimizzazione sarebbe una ragione valida, proprio come un metodo che restituisce un elenco o la necessità di una struttura dati multidimensionale.


1
Molte lingue non hanno nemmeno elenchi e raccolte di array separati. D'altra parte, usare list<T>dove vector<T>funzionerà è un'idea disastrosamente cattiva in C / C ++.
Gort il robot il

1
"Perché un metodo restituisce un array" - questa è una cattiva funzionalità di Java, costringendo i programmatori a catturare il codice con "Arrays.asList (x)". T [] dovrebbe almeno implementare Iterable <T>
kevin cline il

4
@StevenBurnap - è certamente disastrosamente negativo in C, perché non c'è modo di compilare quel codice.
Pete Becker,

@PeteBecker L'espressione vector<T> x compila bene per me in C . :-)
svick

Scusa, intendevo l'equivalente C arrotolato a mano list<T>. Fondamentalmente, ho visto molti problemi di prestazioni causati dagli sviluppatori che utilizzavano gli elenchi per impostazione predefinita quando un array era una scelta migliore.
Gort il robot l'

1

Bene, ho trovato un uso per gli array in un gioco che stavo scrivendo. L'ho usato per creare un sistema di inventario con un numero fisso di slot. Ciò ha comportato numerosi vantaggi:

  1. Sapevo esattamente quante slot di inventario aperto aveva l'oggetto di gioco guardando e vedendo quali slot erano nulli.
  2. Sapevo esattamente in quale indice si trovava ogni articolo.
  3. Era ancora un array "tipizzato" (Item [] Inventory), quindi ho potuto aggiungere / rimuovere gli oggetti di tipo "Item".

Ho pensato che se avessi mai avuto bisogno di "aumentare" la dimensione dell'inventario, avrei potuto farlo trasferendo i vecchi oggetti nel nuovo array, ma poiché l'inventario era stato riparato dallo spazio dello schermo e non avevo bisogno di ingrandirlo dinamicamente / più piccolo, ha funzionato bene per lo scopo per cui lo stavo usando.


1

Se stai attraversando tutti gli elementi di un elenco, allora no, non è necessario un array, la "selezione successiva" o arbitraria "selezione senza sostituzione" andrà bene.

Ma se il tuo algoritmo necessita di un accesso casuale agli elementi nella raccolta, allora sì, è necessario un array.

Questo è in qualche modo analogo a "è necessario andare?". In un linguaggio moderno ragionevole non è affatto necessario. Ma se si staccano le astrazioni, ad un certo punto, è tutto ciò che è effettivamente disponibile per te, cioè l'unico modo per implementare queste astrazioni è con la funzione "non necessaria". (Certo, l'analogia non è perfetta, non credo che nessuno dica che le matrici siano una cattiva pratica di programmazione; sono facili da capire e da pensare).


L'elenco <T> offre anche l'accesso casuale. Certo, è implementato con un array, ma ciò non deve disturbare l'utente. Quindi, nella migliore delle ipotesi, hai offerto un unico motivo per utilizzare le matrici: per implementare List<T>.

@delnan Penso che sia questo il mio punto rispetto alle astrazioni. A volte è meglio pensare in termini di array, non solo per motivi di efficienza. (ovviamente a volte è meglio pensare a un elenco collegato, e molto meglio a quello di pensare a un elenco (senza preoccuparsi dei collegamenti o altrimenti come viene implementato), e molto meglio solo una raccolta.
Mitch

0

Compatibilità legacy.

Esperienza personale di qualsiasi forma:

Programmatori legacy - il mio collega usa array ovunque, lo fa da oltre 30 anni, buona fortuna cambiando idea con le tue nuove idee confuse.

Codice legacy - foo (barra dell'array []) sicuro che puoi usare una funzione toarray list / vector / collection ma se non usi nessuna di queste funzionalità aggiuntive è più facile usare un array per cominciare, spesso più leggibile senza il cambio di tipo.

Capo legacy - il mio capo era un buon programmatore prima che entrasse in gestione molti anni fa e pensa ancora che sia aggiornata, "usare array" può terminare una riunione, spiegando quanto può costare una raccolta a tutti.


3
colleghi che sono 20 anni indietro rispetto alla curva e un capo che vuole sapere quali tipi di dati stai utilizzando ... Adoro questo sito per ricordare a me stesso quanto potrebbe essere peggio il mio lavoro
JoelFan,

1
circa il 40% di ciò che facciamo è il design integrato in cui si svolgono le discussioni in KB, gli piace dirmi che non è obsoleto è lec
specalizzato

0

1) Non esiste una versione multidimensionale di Elenco. Se i tuoi dati hanno più di una dimensione, sarà molto inefficiente utilizzare gli elenchi.

2) Quando hai a che fare con un gran numero di piccoli tipi di dati (ad esempio, una mappa in cui tutto ciò che hai è un byte per il tipo di terreno) ci possono essere notevoli differenze di prestazioni dovute alla memorizzazione nella cache. La versione dell'array carica diversi elementi per memoria, la versione dell'elenco ne carica solo uno. Inoltre, la versione dell'array contiene molte volte più celle nella cache rispetto alla versione dell'elenco: se si elaborano ripetutamente i dati, ciò può fare una grande differenza se la versione dell'array si adatta alla cache ma la versione dell'elenco no.

Per un caso estremo, considera Minecraft. (Sì, non è scritto in C #. Lo stesso motivo si applica.)



0

Una matrice di 100 elementi di qualche tipo T incapsula 100 variabili indipendenti di tipo T. Se T risulta essere un tipo di valore che ha un campo pubblico mutevole di tipo Q e uno di tipo R, allora ciascun elemento della matrice incapsulerà variabili indipendenti dei tipi Q e R. L'array nel suo insieme incapsulerà quindi 100 variabili indipendenti di tipo Q e 100 variabili indipendenti di tipo R; è possibile accedere a ciascuna di queste variabili singolarmente senza influire su nessun'altra. Nessun tipo di raccolta diverso dagli array può consentire l'utilizzo dei campi delle strutture come variabili indipendenti.

Se T invece fosse un tipo di classe con campi mutabili pubblici di tipo Q e R, ogni elemento dell'array contiene l' unico riferimento, in qualsiasi parte dell'universo, a un'istanza di T, e se nessuno degli elementi dell'array lo farà mai essere modificato per identificare un oggetto a cui esiste un riferimento esterno, quindi l'array incapsulerà effettivamente 100 variabili indipendenti di tipo Q e 100 variabili indipendenti di tipo R. Altri tipi di raccolta possono imitare il comportamento di tale array, ma se l'unico scopo dell'array è incapsulare 100 variabili di tipo Q e 100 di tipo R, incapsulare ogni coppia di variabili nel proprio oggetto di classe è un modo costoso per farlo. Ulteriore, utilizza un array o una raccolta di tipo di classe mutabile crea la possibilità che le variabili identificate dagli elementi dell'array possano non essere indipendenti .

Se si suppone che un tipo si comporti come un tipo di oggetto, allora dovrebbe essere un tipo di classe o una struttura di campo privato che non fornisce alcun mezzo di mutazione se non la sostituzione. Se, tuttavia, si suppone che un tipo si comporti come un insieme di variabili correlate ma indipendenti attaccate insieme al nastro adesivo, si dovrebbe usare un tipo che è un insieme di variabili attaccate insieme al nastro adesivo - una struttura a campo esposto . Le matrici di questo tipo sono molto efficienti con cui lavorare e hanno una semantica molto pulita. L'uso di qualsiasi altro tipo porterà a semantica confusa, prestazioni inferiori o entrambe.


0

Una differenza importante è l'allocazione di memoria. Ad esempio, l'attraversamento di un elenco collegato potrebbe comportare un sacco di errori nella cache e prestazioni più lente, mentre un array rappresenta un pezzo di memoria contiguo che contiene più istanze di un determinato tipo di dati e attraversarlo per avere più probabilità di colpire la CPU cache.

Ovviamente, una serie di riferimenti a oggetti potrebbe non beneficiare tanto degli hit della cache, poiché la dereferenziazione potrebbe portarti ovunque in memoria.

Quindi ci sono implementazioni di elenchi come ArrayList, che implementano un elenco usando un array. Sono utili primitivi da avere.


2
La domanda si pone specificamente sul tipo .Net List<T>, che non è implementato utilizzando un elenco collegato, ma utilizzando un array (è essenzialmente equivalente a ArrayList<T>in Java).
svick,

-2

Ecco alcune indicazioni su cui è possibile selezionare Arrayquando e quando selezionare List.

  • Utilizzare Arrayquando si ritorna da un metodo.
  • Utilizzare Listcome variabile quando si costruisce il valore restituito (all'interno del metodo). Quindi utilizzare .ToArray()quando si ritorna dal metodo.

In generale, usa un Arrayquando non intendi che il consumatore aggiunga articoli alla collezione. Utilizzare Listquando si intende che il consumatore deve aggiungere elementi alla raccolta.

Arrayè pensato per gestire collezioni "statiche" mentre Listè pensato per gestire collezioni "dinamiche".


2
La regola che suggerisci è arbitraria e potrebbe comportare un codice inefficiente che esegue copie non necessarie. Per lo meno hai bisogno di una qualche forma di giustificazione per questo.
Jules l'

@Jules Ho trovato l'esperienza degli sviluppatori molto più importante delle micro-ottimizzazioni. Non ho mai avuto problemi di prestazioni a questo livello.
Daniel Lidström,

Tuttavia, non vedo come l'esperienza dello sviluppatore sia migliorata da questa regola. Richiede di indovinare quali client vorranno fare i tuoi codici con i valori restituiti, e rende il tuo codice meno comodo da usare quando lo sbagli, eppure non riesco a vedere alcun vantaggio. Le prestazioni potrebbero essere una preoccupazione secondaria, ma non le sacrificherei per seguire una regola che non guadagna nulla.
Jules,

@Jules Immagino che dipenda dalle tue preferenze allora. Preferisco trattare i valori di ritorno come costanti, in quanto tali preferisco usare Arrayinvece di List. Bello sentire i tuoi pensieri però!
Daniel Lidström,

Hai preso in considerazione l'utilizzo di UmmodifiableLists?
Jules,
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.