Je dois garder en mémoire des milliers de chaînes pour pouvoir y accéder en série. Devrais-je les stocker dans un tableau ou devrais-je utiliser une sorte de liste?
Étant donné que les tableaux conservent toutes les données dans un bloc de mémoire contigu (contrairement aux listes), l’utilisation d’un tableau pour stocker des milliers de chaînes risque-t-elle de poser des problèmes?
Je suggère que vous utilisiez un profileur pour tester ce qui est plus rapide.
Mon opinion personnelle est que vous devriez utiliser des listes.
Je travaille sur une grande base de code et un groupe précédent de développeurs utilisait des tableaux partout. Cela rendait le code très rigide. Après avoir modifié de grandes parties de celle-ci en listes, nous n’avons constaté aucune différence de vitesse.
La méthode Java consiste à considérer quelles données abstraction correspondent le mieux à vos besoins. Rappelez-vous que dans Java, une liste est un résumé, pas un type de données concret. Vous devez déclarer les chaînes en tant que liste, puis l'initialiser à l'aide de l'implémentation ArrayList.
List<String> strings = new ArrayList<String>();
Cette séparation du type de données abstrait et de la mise en œuvre spécifique est l’un des aspects clés de la programmation orientée objet.
ArrayList implémente le type de données abstrait List en utilisant un tableau comme implémentation sous-jacente. La vitesse d’accès est pratiquement identique à un tableau, avec les avantages supplémentaires de pouvoir ajouter et soustraire des éléments à une liste (bien qu’il s’agisse d’une opération O(n) avec une liste de tableaux). changer la mise en œuvre sous-jacente plus tard, vous pouvez. Par exemple, si vous vous rendez compte que vous avez besoin d’un accès synchronisé, vous pouvez modifier l’implémentation en vecteur sans réécrire tout votre code.
En fait, ArrayList a été spécialement conçu pour remplacer la construction de tableau de bas niveau dans la plupart des contextes. Si Java était conçu aujourd'hui, il est tout à fait possible que des tableaux aient été totalement laissés de côté en faveur de la construction ArrayList.
Étant donné que les tableaux conservent toutes les données dans un bloc de mémoire contigu (contrairement aux listes), l'utilisation d'un tableau pour stocker des milliers de chaînes pose-t-elle problème?
En Java, toutes les collections stockent uniquement des références à des objets, pas aux objets eux-mêmes. Les deux tableaux et ArrayList stockeront quelques milliers de références dans un tableau contigu, ils sont donc essentiellement identiques. Vous pouvez considérer qu'un bloc contigu de quelques milliers de références 32 bits sera toujours facilement disponible sur du matériel moderne. Cela ne garantit pas que vous ne manquerez pas de mémoire, bien sûr, mais simplement que le bloc de mémoire contiguë requis n'est pas difficile à remplir.
Vous devriez préférer les types génériques aux tableaux. Comme mentionné par d’autres, les tableaux sont inflexibles et n’ont pas le pouvoir d’expression des types génériques. (Ils prennent toutefois en charge la vérification typographique à l'exécution, mais cela se mélange mal avec les types génériques.)
Mais, comme toujours, lors de l'optimisation, vous devez toujours suivre ces étapes:
Bien que les réponses proposant d'utiliser ArrayList aient un sens dans la plupart des scénarios, la question de la performance relative n'a pas vraiment été résolue.
Il y a quelques choses que vous pouvez faire avec un tableau:
Bien que les opérations get et set soient un peu plus lentes sur une ArrayList (respectivement 1 et 3 nanosecondes par appel sur ma machine), il y a très peu de surcharge liée à l'utilisation d'un ArrayList par rapport à un tableau pour toute utilisation non intensive. Il y a cependant quelques points à garder à l'esprit:
list.add(...)
) est coûteux et vous devez essayer de définir la capacité initiale à un niveau adéquat lorsque cela est possible (notez que le même problème se produit lors de l'utilisation d'un tableau)Voici les résultats que j'ai mesurés pour ces trois opérations à l'aide de bibliothèque d'analyse comparative jmh (fois en nanosecondes) avec JDK 7 sur une machine de bureau x86 standard. Notez que ArrayList n'est jamais redimensionné dans les tests pour vous assurer que les résultats sont comparables. Code de référence disponible ici .
J'ai exécuté 4 tests en exécutant les instructions suivantes:
Integer[] array = new Integer[1];
List<Integer> list = new ArrayList<> (1);
Integer[] array = new Integer[10000];
List<Integer> list = new ArrayList<> (10000);
Résultats (en nanosecondes par appel, 95% de confiance):
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]
Conclusion: pas de différence notable .
J'ai exécuté 2 tests en exécutant les instructions suivantes:
return list.get(0);
return array[0];
Résultats (en nanosecondes par appel, 95% de confiance):
a.p.g.a.ArrayVsList.getArray [2.958, 2.984]
a.p.g.a.ArrayVsList.getList [3.841, 3.874]
Conclusion: obtenir un tableau est environ 25% plus rapide qu'un tableau, même si la différence n'est que de l'ordre de la nanoseconde.
J'ai exécuté 2 tests en exécutant les instructions suivantes:
list.set(0, value);
array[0] = value;
Résultats (en nanosecondes par appel):
a.p.g.a.ArrayVsList.setArray [4.201, 4.236]
a.p.g.a.ArrayVsList.setList [6.783, 6.877]
Conclusion: les opérations sur les tableaux sont environ 40% plus rapides que sur les listes, mais, comme pour get, chaque opération prend quelques nanosecondes. différence pour atteindre 1 seconde, il faudrait définir des éléments dans la liste/tableau des centaines de millions de fois!
Le constructeur de copie de ArrayList délègue à Arrays.copyOf
donc les performances sont identiques à celles de la copie de tableau (copier un tableau via clone
, Arrays.copyOf
ou System.arrayCopy
ne fait aucune différence importante en termes de performances ).
Je suppose que l'affiche originale provient d'un arrière-plan C++/STL, ce qui crée une certaine confusion. En C++, std::list
est une liste doublement chaînée.
In Java [Java.util.]List
est une interface sans implémentation (classe abstraite pure en termes C++). List
peut être une liste doublement chaînée - Java.util.LinkedList
est fourni. Cependant, 99 fois sur 100 lorsque vous voulez créer une nouvelle List
, vous souhaitez utiliser Java.util.ArrayList
, qui est l'équivalent approximatif de C++ std::vector
. Il existe d'autres implémentations standard, telles que celles renvoyées par Java.util.Collections.emptyList()
et Java.util.Arrays.asList()
.
Du point de vue des performances, le fait de devoir passer par une interface et un objet supplémentaire représente un très petit avantage. Toutefois, l'inline-runtime signifie que cela a rarement une signification. Rappelez-vous également que String
sont généralement un objet plus un tableau. Donc, pour chaque entrée, vous avez probablement deux autres objets. En C++ std::vector<std::string>
, bien que copiant par valeur sans pointeur en tant que tel, les tableaux de caractères formeront un objet pour chaîne (et ceux-ci ne seront généralement pas partagés).
Si ce code particulier dépend vraiment des performances, vous pouvez créer un seul tableau char[]
(ou même byte[]
) pour tous les caractères de toutes les chaînes, puis un tableau de décalages. IIRC, voici comment javac est implémenté.
Je conviens que dans la plupart des cas, vous devez choisir la flexibilité et l'élégance de ArrayLists plutôt que des tableaux - et dans la plupart des cas, l'impact sur les performances du programme sera négligeable.
Toutefois, si vous effectuez une itération constante et importante avec peu de changement structurel (sans ajout ni suppression) pour, par exemple, le rendu graphique d'un logiciel ou une machine virtuelle personnalisée, mes tests de benchmarking sur accès séquentiel montrent que Les tableaux ArrayLists sont 1,5 fois plus lents que les tableaux sur mon système (Java 1.6 sur mon iMac âgé d’un an).
Un code:
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
}
}
Eh bien, premièrement, cela vaut la peine de préciser que vous voulez dire "liste" dans le sens classique des structures de données comp sci (c'est-à-dire une liste chaînée) ou voulez-vous dire Java.util.List? Si vous voulez parler de Java.util.List, c'est une interface. Si vous souhaitez utiliser un tableau, utilisez simplement l'implémentation ArrayList et vous obtiendrez un comportement et une sémantique semblables à ceux d'un tableau. Problème résolu.
Si vous voulez dire un tableau vs une liste chaînée, c'est un argument légèrement différent pour lequel nous revenons à Big O (voici un explication en anglais clair s'il s'agit d'un terme inconnu.
Tableau;
Liste liée:
Vous choisissez donc celui qui convient le mieux à la façon dont vous redimensionnez votre tableau. Si vous redimensionnez, insérez et supprimez beaucoup, une liste chaînée est peut-être un meilleur choix. Il en va de même si l'accès aléatoire est rare. Vous parlez d'accès série. Si vous effectuez principalement un accès série avec très peu de modifications, le choix de votre choix importe peu.
Les listes chaînées ont un surcoût légèrement plus élevé puisque, comme vous le dites, vous avez affaire à des blocs de mémoire potentiellement non contigus et à des pointeurs (effectifs) sur l'élément suivant. Ce n'est probablement pas un facteur important, sauf si vous traitez avec des millions d'entrées.
J'ai écrit un petit repère pour comparer ArrayLists à Arrays. Sur mon ancien ordinateur portable, le temps nécessaire pour parcourir une liste de 5000 éléments, 1000 fois, était environ 10 millisecondes plus lent que le code de tableau équivalent.
Donc, si vous ne faites que réitérer la liste et que vous le faites souvent, alors peut-être cela vaut l'optimisation. Sinon, j'utiliserais la liste, car cela faciliterait la tâche lorsque vous faites devez optimiser le code.
nb J'ai remarqué que l'utilisation de for String s: stringsList
était environ 50% plus lente que l'utilisation d'une ancienne boucle for pour accéder à la liste. Allez comprendre ... Voici les deux fonctions que j'ai chronométrées; le tableau et la liste ont été remplis avec 5000 chaînes aléatoires (différentes).
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();
}
}
}
Non, car techniquement, le tableau ne stocke que la référence aux chaînes. Les chaînes elles-mêmes sont allouées dans un emplacement différent. Pour un millier d'éléments, je dirais qu'une liste serait meilleure, elle est plus lente, mais elle offre plus de flexibilité et est plus facile à utiliser, surtout si vous souhaitez les redimensionner.
Si vous en avez des milliers, envisagez d’utiliser un trie. Un tri est une structure arborescente qui fusionne les préfixes communs de la chaîne stockée.
Par exemple, si les chaînes étaient
intern
international
internationalize
internet
internets
Le trie stockerait:
intern
-> \0
international
-> \0
-> ize\0
net
->\0
->s\0
Les chaînes nécessitent 57 caractères (y compris le terminateur null, '\ 0') pour le stockage, plus quelle que soit la taille de l'objet String qui les contient. (En vérité, nous devrions probablement arrondir toutes les tailles jusqu'à des multiples de 16, mais ...) Appelez-le 57 + 5 = 62 octets, à peu près.
Le fichier requiert 29 (y compris le terminateur nul, '\ 0'), plus la taille des trois noeuds, qui sont une référence à un tableau et une liste de noeuds enfant.
Pour cet exemple, cela revient probablement à peu près au même; pour des milliers, il en sort probablement moins tant que vous avez des préfixes communs.
Maintenant, lorsque vous utilisez le test dans un autre code, vous devez convertir en String, en utilisant probablement un StringBuffer comme intermédiaire. Si de nombreuses chaînes sont utilisées simultanément comme chaînes, en dehors de la trie, c'est une perte.
Mais si vous n’en utilisez que quelques-uns à l’heure, par exemple, pour rechercher des éléments dans un dictionnaire, cet outil peut vous faire gagner beaucoup d’espace. Définitivement moins d'espace que de les stocker dans un HashSet.
Vous dites que vous y accédez "en série" - si cela signifie séquentiellement un ordre alphabétique, le test vous donne également un ordre alphabétique gratuit, si vous le parcourez en profondeur d'abord.
Puisqu'il y a déjà beaucoup de bonnes réponses ici, je voudrais vous donner quelques informations supplémentaires sous forme de vue pratique, à savoir comparaison de performance d'insertion et d'itération: tableau primitif vs liste liée en Java.
C'est un contrôle de performance simple et réel.
Le résultat dépendra donc des performances de la machine.
Le code source utilisé pour cela est ci-dessous:
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.");
}
}
Le résultat de performance est ci-dessous:
MISE À JOUR:
Comme Mark l'a noté, il n'y a pas de différence significative après le préchauffage de la machine virtuelle Java (plusieurs passes de test). Vérifié avec un tableau recréé ou même une nouvelle passe commençant par une nouvelle ligne de matrice. Il est fort probable que ce signe simple tableau avec accès à un index ne doit pas être utilisé en faveur de collections.
Encore les premiers 1-2 passes simples tableau est 2-3 fois plus rapide.
POSTE ORIGINAL:
Trop de mots pour le sujet trop simple à vérifier. Sans aucun tableau de questions est plusieurs fois plus rapide que n'importe quel conteneur de classe. Je cours sur cette question à la recherche d'alternatives pour ma section critique de performance. Voici le code prototype que j'ai construit pour vérifier la situation réelle:
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);
}
}
Et voici la réponse:
Basé sur le tableau (la ligne 16 est active):
Time: 7064
Basé sur la liste (la ligne 17 est active):
Time: 20950
Plus de commentaires sur 'plus vite'? Ceci est bien compris. La question est de savoir quand environ 3 fois plus vite est mieux pour vous que la flexibilité de la liste. Mais ceci est une autre question. En passant, j’ai aussi vérifié cela en me basant sur ArrayList
construit manuellement. Presque le même résultat.
Le choix entre tableau et liste n'est pas très important (compte tenu des performances) dans le cas du stockage d'objets chaîne. Parce que le tableau et la liste vont stocker les références d'objet de chaîne, pas les objets réels.
N'oubliez pas qu'une ArrayList encapsule un tableau, il y a donc peu de différence par rapport à l'utilisation d'un tableau primitif (à l'exception du fait qu'il est beaucoup plus facile d'utiliser une liste en Java).
Le seul cas où il est logique de préférer un tableau à un ArrayList est lorsque vous stockez des primitives, c'est-à-dire byte, int, etc.
Si vous connaissez à l'avance la taille des données, un tableau sera plus rapide.
Une liste est plus flexible. Vous pouvez utiliser une liste de tableaux qui est sauvegardée par un tableau.
la liste est plus lente que les tableaux. Si vous avez besoin d'efficacité, utilisez les tableaux. Si vous avez besoin de souplesse, utilisez la liste.
Si vous pouvez vivre avec une taille fixe, les tableaux seront plus rapides et auront besoin de moins de mémoire.
Si vous avez besoin de la souplesse de l’interface List pour l’ajout et la suppression d’éléments, il reste à déterminer quelle implémentation choisir. ArrayList est souvent recommandé et utilisé dans tous les cas, mais ArrayList a également des problèmes de performances si des éléments au début ou au milieu de la liste doivent être supprimés ou insérés.
Vous voudrez peut-être jeter un coup d'œil à http://Java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-list qui présente GapList. Cette nouvelle implémentation de liste combine les forces d'ArrayList et de LinkedList, ce qui permet d'obtenir de très bonnes performances pour presque toutes les opérations.
Aucune des réponses ne contenait d'informations qui m'intéressait - analyse répétitive du même tableau plusieurs fois. Il a fallu créer un test JMH pour cela.
Résultats (Java 1.8.0_66 x32, l'itération d'un tableau brut est au moins 5 fois plus rapide que 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);
}
}
La liste est la méthode préférée dans Java 1.5 et au-delà, car elle peut utiliser des génériques. Les tableaux ne peuvent pas avoir de génériques. De plus, les tableaux ont une longueur prédéfinie, qui ne peut pas croître de manière dynamique. Initialiser un tableau de grande taille n’est pas une bonne idée. ArrayList est le moyen de déclarer un tableau avec des génériques et il peut croître de manière dynamique. Toutefois, si les opérations de suppression et d’insertion sont utilisées plus fréquemment, la liste chaînée est la structure de données la plus rapide à utiliser.
Les tableaux recommandés partout où vous les utilisez peuvent être utilisés à la place de la liste, en particulier si vous savez que le nombre et la taille des éléments ne changeraient pas.
Voir Oracle Java meilleure pratique: http://docs.Oracle.com/cd/A97688_16/generic.903/bp/Java.htm#1007056
Bien sûr, si vous avez besoin d'ajouter et de supprimer des objets de la collection de nombreuses fois, utilisez des listes faciles.
"Des milliers" n'est pas un grand nombre. Quelques milliers de chaînes de longueur de paragraphe ont une taille de l'ordre de quelques mégaoctets. Si tout ce que vous voulez faire, c'est accéder à ces éléments en série, utilisez ne liste immuable à liens simples .
Je suis venu ici pour avoir une meilleure idée de l'impact sur les performances de l'utilisation de listes sur des tableaux. J'ai dû adapter le code ici pour mon scénario: tableau/liste de ~ 1000 ints utilisant principalement des accesseurs, ce qui signifie tableau [j] vs list.get (j)
En prenant le meilleur des 7 pour ne pas être scientifique à ce sujet (les premiers avec une liste où 2.5x plus lent), je reçois ceci:
array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator
array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)
- donc, environ 30% plus rapide avec array
La deuxième raison pour laquelle nous postons maintenant est que personne ne mentionne l'impact si vous utilisez un code mathématique/matrice/simulation/optimisation avec des boucles imbriquées.
Supposons que vous ayez trois niveaux imbriqués et que la boucle interne soit deux fois plus lente que vous regardez 8 fois la performance. Quelque chose qui fonctionnerait en un jour prend maintenant une semaine.
* EDIT Assez choqué ici, pour les coups de pied, j'ai essayé de déclarer int [1000] plutôt qu'Integer [1000]
array int[] best 299ms iterator
array int[] best 296ms getter
L'utilisation d'Integer [] par rapport à int [] représente un double résultat. ListArray avec itérateur est 3 fois plus lente que int []. Je pensais vraiment que les implémentations de liste de Java étaient similaires aux tableaux natifs ...
Code de référence (appel plusieurs fois):
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);
}
En fonction de la mise en œuvre. il est possible qu'un tableau de types primitifs soit plus petit et plus efficace que ArrayList. En effet, le tableau stockera les valeurs directement dans un bloc de mémoire contigu, alors que la plus simple implémentation de ArrayList stockera les pointeurs vers chaque valeur. Sur une plate-forme 64 bits en particulier, cela peut faire une énorme différence.
Bien sûr, il est possible que l’implémentation de jvm ait un cas spécial pour cette situation, auquel cas la performance sera la même.
Ne tombez pas dans le piège de l'optimisation sans un benchmarking approprié. Comme d'autres l'ont suggéré, utilisez un profileur avant de faire n'importe quelle hypothèse.
Les différentes structures de données que vous avez énumérées ont des objectifs différents. Une liste est très efficace pour insérer des éléments au début et à la fin, mais souffre beaucoup lors de l'accès à des éléments aléatoires. Un tableau a un stockage fixe mais fournit un accès aléatoire rapide. Enfin, une ArrayList améliore l'interface d'un tableau en lui permettant de se développer. Normalement, la structure de données à utiliser doit être dictée par la manière dont les données stockées seront accessibles ou ajoutées.
À propos de la consommation de mémoire. Vous semblez mélanger certaines choses. Un tableau ne vous donnera qu'une quantité continue de mémoire pour le type de données que vous avez. N'oubliez pas que Java a des types de données fixes: boolean, char, int, long, float et Object (cela inclut tous les objets, même un tableau est un objet). Cela signifie que si vous déclarez un tableau de chaînes String [1000] ou MyObject myObjects [1000], vous n'obtenez que 1 000 boîtes de mémoire suffisamment grandes pour stocker l'emplacement (références ou pointeurs) des objets. Vous n'obtenez pas une mémoire de 1000 boîtes suffisamment grande pour s'adapter à la taille des objets. N'oubliez pas que vos objets sont d'abord créés avec "nouveau". C'est à ce moment que l'allocation de mémoire est effectuée et qu'une référence (leur adresse de mémoire) est ultérieurement stockée dans le tableau. L'objet n'est pas copié dans le tableau, mais uniquement sa référence.
La matrice est plus rapide - toute la mémoire est préallouée à l'avance.
Je ne pense pas que cela fasse une réelle différence pour Strings. Ce qui est contigu dans un tableau de chaînes, ce sont les références aux chaînes, les chaînes elles-mêmes sont stockées à des emplacements aléatoires en mémoire.
Les tableaux par rapport aux listes peuvent faire la différence pour les types primitifs, pas pour les objets. IF vous connaissez à l’avance le nombre d’éléments et n’avez pas besoin de souplesse, un tableau de millions d’entiers ou de doubles sera plus efficace en mémoire et plus rapide en vitesse qu’une liste, car en effet ils seront stockés de manière contiguë et consultés instantanément. C'est pourquoi Java utilise toujours des tableaux de caractères pour les chaînes, des tableaux d'intes pour les données d'image, etc.
Un grand nombre de micro-repères donnés ici ont trouvé des nombres de quelques nanosecondes pour des choses comme les lectures array/ArrayList. Ceci est tout à fait raisonnable si tout est dans votre cache L1.
Un cache de niveau supérieur ou un accès à la mémoire principale peut avoir des ordres de grandeur tels que 10nS-100nS, par rapport à 1nS pour le cache L1. L'accès à ArrayList a une réserve de mémoire supplémentaire, et dans une application réelle, vous pouvez payer ce coût presque jamais à chaque fois, en fonction de ce que votre code fait entre les accès. Et, bien sûr, si vous avez beaucoup de petits ArrayLists, cela peut ajouter à votre utilisation de la mémoire et le rendre plus probable que vous aurez des erreurs de cache.
Il semble que l’affiche originale n’en utilise qu’un seul et accède à de nombreux contenus en peu de temps; il ne devrait donc pas y avoir de grandes difficultés. Mais cela pourrait être différent pour d'autres personnes, et vous devriez faire attention lorsque vous interprétez des microbiens de référence.
Java Strings, cependant, sont un gaspillage épouvantable, en particulier si vous en stockez beaucoup de petits (regardez-les simplement avec un analyseur de mémoire, il semble y avoir plus de 60 octets pour une chaîne de quelques caractères). Un tableau de chaînes a une indirection vers l'objet String, et un autre de l'objet String vers un char [] qui contient la chaîne elle-même. Si quelque chose va détruire votre cache L1, c'est ceci, combiné à des milliers ou des dizaines de milliers de chaînes. Donc, si vous êtes sérieux - vraiment sérieux - pour obtenir le plus de performances possible, vous pouvez envisager de le faire différemment. Vous pouvez, par exemple, tenir deux tableaux, un char [] avec toutes les chaînes qu'il contient, et un int [] avec des décalages par rapport aux débuts. Ce sera un PITA pour faire n'importe quoi, et vous n'en avez presque certainement pas besoin. Et si vous le faites, vous avez choisi la mauvaise langue.
ArrayList utilise en interne un objet tableau pour ajouter (ou stocker) les éléments. En d'autres termes, ArrayList est sauvegardé par Array data -structure.Le tableau de ArrayList est redimensionnable (ou dynamique).
Array est plus rapide que Array car ArrayList utilise en interne un tableau. si nous pouvons ajouter directement des éléments dans Array et indirectement des éléments dans Array via ArrayList, le mécanisme est toujours directement plus rapide que le mécanisme indirect.
Il y a deux méthodes add () surchargées dans la classe ArrayList:
1. add(Object)
: ajoute un objet à la fin de la liste.
2. add(int index , Object )
: insère l'objet spécifié à la position spécifiée dans la liste.
Comment la taille de ArrayList augmente-t-elle dynamiquement?
public boolean add(E e)
{
ensureCapacity(size+1);
elementData[size++] = e;
return true;
}
Le point important à noter à partir du code ci-dessus est que nous vérifions la capacité de ArrayList avant d’ajouter l’élément. EnsureCapacity () détermine quelle est la taille actuelle des éléments occupés et quelle est la taille maximale du tableau. Si la taille des éléments remplis (y compris le nouvel élément à ajouter à la classe ArrayList) est supérieure à la taille maximale du tableau, augmentez la taille du tableau. Mais la taille du tableau ne peut pas être augmentée dynamiquement. Donc, ce qui se passe en interne est le nouveau tableau est créé avec la capacité
Jusqu'à Java 6
int newCapacity = (oldCapacity * 3)/2 + 1;
(Mise à jour) De Java 7
int newCapacity = oldCapacity + (oldCapacity >> 1);
de plus, les données de l'ancien tableau sont copiées dans le nouveau tableau.
Ayant des méthodes overhead dans ArrayList, c'est pourquoi Array est plus rapide que ArrayList
.
Tableaux - Il serait toujours préférable de rechercher plus rapidement les résultats
Listes: permet d’obtenir des résultats lors de l’insertion et de la suppression, car ils peuvent être effectués dans O(1). Cette méthode fournit également des méthodes permettant d’ajouter, d’extraire et de supprimer facilement des données. Beaucoup plus facile à utiliser.
Mais rappelez-vous toujours que l'extraction des données serait rapide lorsque la position d'index dans le tableau où les données sont stockées est connue.
Cela pourrait être réalisé en triant le tableau. Cela augmente donc le temps nécessaire pour récupérer les données (c'est-à-dire: stockage des données + tri des données + recherche de la position où les données sont trouvées). Par conséquent, cela augmente le temps de latence supplémentaire pour extraire les données du tableau, même si elles peuvent extraire les données plus tôt.
Par conséquent, ceci pourrait être résolu avec la structure de données trie ou la structure de données ternaire. Comme discuté ci-dessus, la structure de données trie serait très efficace dans la recherche de données. La recherche d'un mot en particulier peut être effectuée avec une magnitude O(1). Quand le temps compte c'est-à-dire; si vous devez rechercher et récupérer des données rapidement, vous pouvez utiliser la structure de données trie.
Si vous souhaitez que votre espace mémoire soit moins utilisé et que vous souhaitiez obtenir de meilleures performances, utilisez la structure de données ternaire. Ces deux types sont adaptés pour stocker un grand nombre de chaînes (par exemple, comme des mots contenus dans le dictionnaire).
Cela dépend de la façon dont vous devez y accéder.
Après le stockage, si vous voulez principalement effectuer une opération de recherche, avec peu ou pas d'insertion/suppression, passez à Array (la recherche étant effectuée dans O(1) dans les tableaux, alors que l'ajout -ordre des éléments).
Après le stockage, si votre but principal est d’ajouter/supprimer des chaînes, avec peu ou pas d’opération de recherche, choisissez alors Liste.