Y a-t-il une raison d'utiliser un tableau lorsque des listes sont disponibles? [fermé]


30

Il semble List<T>qu'en C # peut faire tout ce qu'un tableau peut faire et plus, et semble aussi efficace en mémoire et en performances qu'un tableau.

Alors pourquoi voudrais-je jamais utiliser un tableau?

Je ne pose évidemment pas de questions sur les cas où une API ou une autre contrainte externe (c'est-à-dire la fonction Main) m'oblige à utiliser un tableau ... Je ne pose que des questions sur la création de nouvelles structures de données dans mon propre code.


56
À mettre en œuvreList<T>
ratchet freak

43
is also just as efficient in memory and performance as an array- euh. D'où tenez-vous cette notion?
Oded

12
Plusieurs dimensions comme var test = new string[5,5];)
Knerd

7
Il y a aussi le tableau d'octets [] couramment utilisé.
SBoss

11
Pour C # en particulier, Eric Lippert a écrit à ce sujet dans blogs.msdn.com/b/ericlippert/archive/2008/09/22/… .
Mephy

Réponses:


26

Pour la même raison, je ne conduis pas de camion pour aller travailler. Je n'utilise pas quelque chose dont je n'utiliserai pas les fonctionnalités.

Tout d'abord, un tableau est une construction primitive, donc un tableau est plus rapide et plus efficace qu'un List <> bien sûr, donc votre argument n'est pas vrai. Array est également disponible partout et connu des développeurs utilisant différents langages et plates-formes.

La raison la plus importante pour laquelle j'utilise un tableau au lieu d'une liste <> est d'impliquer que les données sont de longueur fixe . Si je n'ajoute ou ne supprime aucun élément de cette collecte de données, je veux m'assurer que le type reflète cela.

Autre chose, disons que vous implémentez une nouvelle structure de données et que vous avez lu des articles à ce sujet. Maintenant, lors de l'implémentation d'algorithmes spécifiques, vous ne pouvez pas toujours vous fier à l'implémentation d'un type général par quelqu'un d'autre. Il passe de .NET à Mono et même entre différentes versions du framework.

Et il est parfois plus facile de porter un morceau de code qui utilise un tableau au lieu d'un type dépendant du framework.


4
Comment un tableau est-il "plus rapide" qu'une liste, étant donné qu'il List<T>est implémenté à l'aide d'un tableau? Si vous connaissez le nombre d'éléments au préalable (ce que vous devez savoir lorsque vous utilisez un tableau), vous pouvez également utiliser ces connaissances lors de l'initialisation de la liste.
Theodoros Chatzigiannakis

1
@TheodorosChatzigiannakis Lors de l'utilisation d'un tableau, nous allouons la mémoire et faisons simplement des affectations, plain malloc (), pas de surcharge. Lorsque vous utilisez List <>, vous "ajouterez" des éléments et lorsque vous ajouterez des éléments, List <> effectue une vérification des limites et appelle la méthode EnsureCapacity qui copiera le contenu dans un autre tableau nouvellement alloué si la capacité actuelle n'est pas suffisante. Cela signifie que si vous n'avez pas besoin d'allouer dynamiquement de la nouvelle mémoire, les performances de List ne sont pas aussi bonnes que celles d'un tableau.
Mert Akcakaya

4
Ce que vous dites est juste, mais il suppose que ce soit , nous ne savons pas le nombre d'éléments à l' avance (dans ce cas, les tableaux ne sont pas de toute façon utile) ou que nous ne connaître le nombre d'éléments à l' avance mais nous ne délibérément pas l' utilisation dans le cas de la liste. Si nous faisons utiliser le nombre d'éléments pour initialiser la liste avec la capacité souhaitée, nous obtenons une seule allocation de tableau (jusqu'à ce que la capacité), donc nous payons les mêmes coûts de performance. Toutes les autres opérations courantes sont égales (recherche, récupération par index, affectation par index), à l'exception des ajouts supplémentaires (que les tableaux n'ont pas du tout).
Theodoros Chatzigiannakis

3
@TheodorosChatzigiannakis: les tableaux sont plus rapides que les listes. Exécutez simplement une référence simple pour vérifier.
Mehrdad

1
@TheodorosChatzigiannakis, Oui, mais il y a encore des vérifications des limites.
Mert Akcakaya

18

Vous avez besoin de tableaux pour gérer votre collection de structures mutables , bien sûr, et que ferions-nous sans celles-ci.

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

(notez qu'il peut y avoir des cas où un tableau de structures mutables est souhaitable, mais généralement ce comportement différent des structures mutables dans les tableaux par rapport aux autres collections est une source d'erreurs qui doivent être évitées)


Plus sérieusement, vous avez besoin d'un tableau si vous voulez passer un élément par référence . c'est à dire

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

Cela peut être utile pour le code threadsafe sans verrouillage.


Vous avez besoin d'un tableau si vous souhaitez initialiser rapidement et efficacement votre collection de taille fixe avec la valeur par défaut .

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

(notez qu'il serait possible d'implémenter un constructeur pour List qui fait de même, c'est juste que c # n'offre pas cette fonctionnalité)


vous avez besoin d'un tableau si vous souhaitez copier efficacement des parties de la collection

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

(encore une fois, c'est quelque chose qui pourrait également être implémenté pour List, mais cette fonctionnalité n'existe pas en c #)


Si un type est censé représenter un groupe de variables liées mais indépendantes collées avec du ruban adhésif (par exemple les coordonnées d'un point), une structure de champ exposé est la meilleure représentation. Si une variable est censée contenir un groupe de ces groupes, un tableau de ce type de structure est la meilleure représentation. On ne devrait pas utiliser des structures mutables dans les endroits où l'on veut un objet, mais cela ne signifie pas que l'on devrait utiliser des objets ou des choses qui prétendent être des objets dans des endroits où l'on veut un tas de variables collées avec du ruban adhésif.
supercat

Dans certaines situations, vous souhaiterez peut-être une structure mutable, par exemple lorsque vous avez besoin du dernier bit de performances et que vous ne pouvez pas vous permettre des allocations et des références de tas, et dans ce cas, un tableau de structures est votre meilleur choix. Bon essai ici . Mais je ne serais pas d'accord qu'une classe ne devrait pas représenter "un tas de variables collées ensemble". Conceptuellement, les structures et les classes sont essentiellement les mêmes, les différences sont principalement dues aux détails d'implémentation.
HugoRune

Si l'on souhaite utiliser une collection de références d'objets mutables comme une collection de groupes indépendants de variables indépendantes, il faut établir et maintenir un invariant que chaque élément identifie un objet auquel aucune autre référence non éphémère n'existe nulle part dans l'univers. Cela peut certainement être fait (et c'est souvent le cas), mais il n'y a rien dans le langage ou le cadre pour aider le programmeur à maintenir cet invariant. En revanche, un tableau de longueur 100 d'un type de structure avec quatre champs d'instance encapsulera automatiquement et en permanence 400 variables indépendantes.
supercat

Je pense que ces commentaires ne sont pas le bon endroit pour discuter davantage de cette question, et vu que vous avez ajouté votre propre réponse , il ne semble pas nécessaire de reproduire ces informations ici. Il suffit de dire que les tableaux peuvent être utilisés pour gérer des structures mutables. La question de savoir si et quand utiliser le comportement d'implémentation de ces structures pour appliquer certaines contraintes de données dépasse vraiment le cadre de ma réponse.
HugoRune

15

Alors pourquoi voudrais-je jamais utiliser un tableau?

Rarement , vous aurez un scénario où vous savez que vous avez besoin d'un nombre fixe d'éléments. Du point de vue de la conception, cela devrait être évité. Si vous avez besoin de 3 choses, la nature de l'entreprise signifie que vous en aurez très souvent besoin dans la prochaine version.

Pourtant, lorsque ce scénario rare se produit, l'utilisation d'un tableau pour appliquer cet invariant de taille fixe est utile. Il signale aux autres programmeurs qu'il s'agit d'une taille fixe et aide à prévenir toute utilisation abusive lorsque quelqu'un ajoute ou supprime un élément, ce qui brise les attentes ailleurs dans le code.


23
La taille des tableaux n'est pas figée au moment où on écrit le code. Il peut, et est souvent, une variable d'exécution. Il ne change tout simplement pas après la création du tableau .

11
Il n'est pas inhabituel de traiter un nombre fixe d'éléments. Si vous travaillez avec des points 3D, vous ne travaillerez probablement pas avec des points 5D à l'avenir. Le plus gros problème avec les tableaux est qu'ils sont mutables et n'ont pas d'étiquettes pratiques attachées à leurs éléments.
Doval

3
@JanDvorak Les listes peuvent rétrécir ou augmenter et n'ont donc aucun sens dans un contexte impliquant un nombre fixe d'éléments.
Doval

9
Rarement? Tout le monde ne travaille pas sur des monstruosités d'entreprise ultra-configurables super compliquées.
whatsisname

3
Il est très courant d'avoir un nombre fixe d'éléments. Assurez-vous simplement que la longueur est définie par une constante, donc lorsque la prochaine version augmentera le nombre d'éléments de 3 à 4, vous changerez simplement la constante et tout ce qui en dépend sera adapté.
Hans

9

Votre question a déjà été répondue auparavant .

et semble également aussi efficace en mémoire et en performances qu'un tableau.

Ça ne l'est pas. De la question que j'ai liée:

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

Les tableaux sont deux fois plus rapides dans certains cas importants. Je suis certain que l'utilisation de la mémoire diffère également de manière non triviale.

Puisque la prémisse principale de votre question est ainsi défaite, je suppose que cela répond à votre question. En plus de cela, des tableaux vous sont parfois imposés par l'API Win32, le shader de votre GPU ou une autre bibliothèque non DotNet.

Même au sein de DotNet, certaines méthodes consomment et / ou renvoient des tableaux (tels que String.Split). Ce qui signifie que vous devez maintenant manger le coût de l'appelToList et ToArraytout le temps, ou vous devez vous conformer et utiliser un tableau, poursuivant éventuellement le cycle en le propageant aux pauvres utilisateurs en aval de votre code.

Plus de questions et réponses sur Stack Overflow sur ce sujet:


Fait intéressant, cette réponse souligne que si vous utilisez des boucles indexées à l'ancienne ( pour au lieu de foreach ), la différence de performances est minime.
user949300

3

En plus des raisons énumérées dans d'autres réponses, le littéral de tableau prend moins de caractères à déclarer:

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

L'utilisation de array au lieu de Listrend le code un peu plus court et juste un peu plus lisible dans les cas où (1) vous devez passerIEnumerable<T> littéral, ou (2) où d'autres fonctionnalités de Listn'ont pas d'importance et vous devez utiliser une liste littéral.

Je l'ai fait de temps en temps lors de tests unitaires.


1
Oui, juste pour ajouter. Cela fonctionne aussi si vous devez passer un IList <T>
Esben Skov Pedersen

+1, j'ai souvent une opération pour quelques objets du même type qui ne sont pas dans une collection. C'est facile, court et clair d'écrire foreach( var x in new []{ a, b, c ) ) DoStuff( x )ou new []{ a, b, c ).Select( ... )etc
stijn

3

C'est strictement du point de vue OO.

Bien que je ne puisse pas penser à une raison de passer juste un tableau, je peux certainement voir des situations où une représentation de tableau interne à la classe est probablement le meilleur choix.

Bien qu'il existe d'autres options qui donnent des caractéristiques similaires, aucune ne semble aussi intuitive qu'un tableau pour les problèmes de traitement des permutations, imbriqué pour les boucles, la représentation matricielle, les bitmaps et les algorithmes d'entrelacement des données.

Il existe un nombre important de domaines scientifiques qui reposent largement sur les mathématiques matricielles. (par exemple, traitement d'image, correction d'erreurs de données, traitement numérique du signal, une série de problèmes mathématiques appliqués) La plupart des algorithmes dans ces domaines sont écrits en termes d'utilisation de matrices / matrices multidimensionnelles. Il serait donc plus naturel de mettre en œuvre les algorithmes tels qu'ils sont définis plutôt que de les rendre plus «logiciels» conviviaux au détriment de la perte des liens directs avec les documents sur lesquels les algorithmes sont basés.

Comme je l'ai dit, dans ces cas, vous pouvez probablement vous en sortir en utilisant des listes, mais cela ajoute une couche de complexité supplémentaire à des algorithmes déjà complexes.


3

Cela vaut en fait pour d'autres langages qui ont également des listes (comme Java ou Visual Basic). Dans certains cas, vous devez utiliser un tableau car une méthode renvoie un tableau au lieu d'une liste.

Dans un programme réel, je ne pense pas qu'un tableau sera utilisé très souvent, mais parfois vous savez que les données seront de taille fixe et vous aimez le petit gain de performances que vous obtenez en utilisant un tableau. La micro-optimisation serait une raison valable, tout comme une méthode renvoyant une liste, ou le besoin d'une infrastructure de données multidimensionnelle.


1
De nombreuses langues n'ont même pas de collections de listes et de tableaux distinctes. D'un autre côté, utiliser list<T>where vector<T>will work est une mauvaise idée désastreuse en C / C ++.
Gort le robot

1
"Parce qu'une méthode retourne un tableau" - c'est une fausse caractéristique de Java, forçant les programmeurs à chier le code avec "Arrays.asList (x)". T [] devrait au moins implémenter Iterable <T>
kevin cline

4
@StevenBurnap - c'est certainement désastreusement mauvais en C, car il n'y a aucun moyen de compiler ce code.
Pete Becker

@PeteBecker L'expression vector<T> x compile très bien pour moi en C . :-)
svick

Désolé, je voulais dire la main-roulé C équivalent à list<T>. Fondamentalement, j'ai vu beaucoup de problèmes de performances causés par des développeurs utilisant simplement des listes par défaut lorsqu'un tableau était un meilleur choix.
Gort le robot

1

Eh bien, j'ai trouvé une utilisation pour les tableaux dans un jeu que j'écris. Je l'ai utilisé pour créer un système d'inventaire avec un nombre fixe de slots. Cela avait plusieurs avantages:

  1. Je savais exactement combien d'emplacements d'inventaire ouverts l'objet de jeu avait en regardant et en voyant quels emplacements étaient nuls.
  2. Je savais exactement dans quel index se trouvait chaque élément.
  3. C'était toujours un tableau "typé" (inventaire Item []), donc je pouvais ajouter / supprimer les objets de type "Item".

Je me suis dit que si jamais je devais "augmenter" la taille de l'inventaire, je pouvais le faire en transférant les anciens éléments dans le nouveau tableau, mais comme l'inventaire était fixé par l'espace d'écran et je n'avais pas besoin de l'agrandir dynamiquement / plus petit, il fonctionnait bien dans le but pour lequel je l'utilisais.


1

Si vous parcourez tous les éléments d'une liste, alors non, un tableau n'est pas nécessaire, la sélection «suivante» ou arbitraire sans remplacement fera l'affaire.

Mais si votre algorithme a besoin d'un accès aléatoire aux éléments de la collection, alors, oui, un tableau est nécessaire.

Ceci est quelque peu analogue à "est-il nécessaire?". Dans une langue moderne raisonnable, ce n'est pas du tout nécessaire. Mais si vous décollez les abstractions, à un moment donné, c'est tout ce qui vous est réellement disponible, c'est-à-dire que la seule façon de mettre en œuvre ces abstractions est avec la fonctionnalité `` inutile ''. (Bien sûr, l'analogie n'est pas parfaite, je pense que personne ne dit que les tableaux sont de mauvaises pratiques de programmation; ils sont faciles à comprendre et à penser).


La liste <T> offre également un accès aléatoire. Bien sûr, il est implémenté avec un tableau, mais cela n'a pas besoin de vous déranger, l'utilisateur. Donc, au mieux, vous avez proposé une seule raison d'utiliser des tableaux: pour implémenter List<T>.

@delnan Je pense que c'est mon point en ce qui concerne les abstractions. Parfois, il vaut mieux penser en termes de tableau, pas seulement pour des raisons d'efficacité. (bien sûr, parfois, il est préférable de penser à une liste chaînée, et bien mieux que de penser à une liste (sans se soucier des liens ou autrement de la façon dont elle est mise en œuvre), et bien mieux qu'une collection.
Mitch

0

Compatibilité héritée.

Tous forment une expérience personnelle:

Programmeurs hérités - mon collègue utilise des tableaux partout, depuis plus de 30 ans, bonne chance pour changer d'avis avec vos nouvelles idées.

Code hérité - foo (barre de tableau []) sûr que vous pouvez utiliser une fonction toarray liste / vecteur / collection mais si vous n'utilisez aucune de ces fonctionnalités supplémentaires, il est plus facile d'utiliser un tableau pour commencer, souvent plus lisible sans changement de type.

Patron hérité - mon patron était un bon programmeur avant de se lancer dans la gestion il y a de nombreuses années et pense toujours qu'elle est à jour, "utiliser des tableaux" peut mettre fin à une réunion, expliquant ce qu'est une collection peut coûter à tout le monde un déjeuner.


3
des collègues qui ont 20 ans de retard et un patron qui veut savoir quels types de données vous utilisez ... J'adore ce site pour me rappeler à quel point mon travail pourrait être
pire

1
environ 40% de ce que nous faisons est de la conception intégrée où les discussions se déroulent en Ko, il aime me dire qu'il n'est pas obsolète hes spécalisé lol
Skeith

0

1) Il n'y a pas de version multidimensionnelle de List. Si vos données ont plus d'une dimension, il sera très inefficace d'utiliser des listes.

2) Lorsque vous traitez avec un grand nombre de petits types de données (par exemple, une carte où tout ce que vous avez est d'un octet pour le type de terrain), il peut y avoir des différences de performances considérables en raison de la mise en cache. La version du tableau charge plusieurs éléments par lecture de mémoire, la version de liste n'en charge qu'un seul. De plus, la version de la baie contient plusieurs fois plus de cellules dans le cache que la version de la liste - si vous traitez les données à plusieurs reprises, cela peut faire une grande différence si la version de la baie tient dans le cache, mais pas la version de la liste.

Pour un cas extrême, considérez Minecraft. (Ouais, ce n'est pas écrit en C #. La même raison s'applique.)



0

Un tableau de 100 éléments d'un certain type T encapsule 100 variables indépendantes de type T. Si T se trouve être un type de valeur qui a un champ public mutable de type Q et un de type R, alors chaque élément du tableau encapsulera des variables indépendantes des types Q et R. L'ensemble du tableau encapsulera ainsi 100 variables indépendantes de type Q et 100 variables indépendantes de type R; n'importe laquelle de ces variables est accessible individuellement sans affecter aucune autre. Aucun type de collection autre que les tableaux ne peut permettre aux champs de structures d'être utilisés comme variables indépendantes.

Si T se trouvait à la place être un type de classe avec des champs publics mutables de type Q et R, chaque élément du tableau contient la seule référence, n'importe où dans l'univers, à une instance d'T , et si aucun des éléments du tableau ne sera jamais être modifié pour identifier un objet auquel existe une référence extérieure, alors le tableau encapsulera efficacement 100 variables indépendantes de type Q et 100 variables indépendantes de type R. D'autres types de collection peuvent imiter le comportement d'un tel tableau, mais si le seul but du tableau consiste à encapsuler 100 variables de type Q et 100 de type R, encapsuler chaque paire de variables dans son propre objet de classe est un moyen coûteux de le faire. Plus loin,l'utilisation d'un tableau ou d'une collection de types de classes mutables crée la possibilité que les variables identifiées par les éléments du tableau ne soient pas indépendantes .

Si un type est censé se comporter comme une sorte d'objet, il doit s'agir d'un type de classe ou d'une structure de champ privé qui ne fournit aucun autre moyen de mutation que le remplacement. Si, cependant, un type est censé se comporter comme un tas de variables liées mais indépendantes collées ensemble avec du ruban adhésif, alors on devrait utiliser un type qui est un tas de variables collées ensemble avec du ruban adhésif - une structure de champ exposé . Les tableaux de ces types sont très efficaces pour travailler avec, et ont une sémantique très propre. L'utilisation de tout autre type entraînera une sémantique confuse, des performances inférieures ou les deux.


0

Une différence importante est l'allocation de mémoire. Par exemple, parcourir une liste chaînée peut entraîner de nombreux échecs de cache et des performances plus lentes, tandis qu'un tableau représente un morceau de mémoire contigu contenant plusieurs instances d'un type de données particulier, et le faire dans l'ordre est plus susceptible de toucher le processeur cache.

Bien sûr, un tableau de références d'objets peut ne pas bénéficier autant des accès au cache, car le déréférencement peut toujours vous emmener n'importe où en mémoire.

Ensuite, il existe des implémentations de liste telles que ArrayList, qui implémentent une liste à l'aide d'un tableau. Ce sont des primitives utiles à avoir.


2
La question porte spécifiquement sur le type .Net List<T>, qui n'est pas implémenté à l'aide d'une liste chaînée, mais à l'aide d'un tableau (il est essentiellement équivalent à ArrayList<T>Java).
svick

-2

Voici quelques conseils que vous pouvez utiliser pour sélectionner Arrayet quand sélectionner List.

  • A utiliser Arraylors du retour d'une méthode.
  • Utilisez-la Listcomme variable lorsque vous construisez la valeur de retour (à l'intérieur de la méthode). Ensuite, utilisez .ToArray()lors du retour de la méthode.

En général, utilisez un Arraylorsque vous ne souhaitez pas que le consommateur ajoute des articles à la collection. À utiliser Listlorsque vous souhaitez que le consommateur ajoute des articles à la collection.

Arrayest destiné à traiter des collections "statiques" alors qu'il Listest destiné à traiter des collections "dynamiques".


2
La règle que vous proposez est arbitraire et risque d'entraîner un code inefficace qui effectue une copie inutile. À tout le moins, vous avez besoin d'une forme de justification pour cela.
Jules

@Jules J'ai trouvé que l'expérience des développeurs était bien plus importante que les micro-optimisations. Je n'ai jamais de problèmes de performances à ce niveau.
Daniel Lidström

Cependant, je ne vois pas comment l'expérience des développeurs est améliorée par cette règle. Cela vous oblige à deviner quels clients de votre code voudront faire avec les valeurs renvoyées, et rend votre code moins pratique à utiliser lorsque vous vous trompez, et pourtant je ne vois aucun avantage à cela. La performance peut être une préoccupation secondaire, mais je ne la sacrifierais pas pour suivre une règle qui ne gagne rien.
Jules

@Jules, je suppose que cela dépend de vos préférences. Je préfère traiter les valeurs de retour comme des constantes, en tant que telles, je préfère utiliser à la Arrayplace de List. Ravi d'entendre vos pensées!
Daniel Lidström

Avez-vous envisagé d'utiliser UmmodifiableLists?
Jules
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.