Maison >Java >javaDidacticiel >Résumé des questions et réponses d'entretien sur les collections Java
Il existe des collections dans chaque langage de programmation, et la version initiale de Java comprenait plusieurs classes de collection : Vector, Stack, HashTable et Array. Avec l'utilisation généralisée des collections, Java 1.2 a proposé un cadre de collection qui inclut toutes les interfaces, implémentations et algorithmes de collection. Java suit depuis longtemps le processus d'utilisation de classes génériques et de classes de collection concurrentes tout en garantissant la sécurité des threads. Il inclut également le blocage des interfaces et leurs implémentations dans le package de concurrence Java. Certains des avantages du framework de collection sont les suivants :
(1) Réduisez les coûts de développement en utilisant des classes de collection de base au lieu d'implémenter nos propres classes de collection.
(2) La qualité du code sera améliorée grâce à l'utilisation de classes de framework de collection rigoureusement testées.
(3) En utilisant les classes de collection incluses avec le JDK, les coûts de maintenance du code peuvent être réduits.
(4) Réutilisabilité et opérabilité.
Java 1.5 a introduit les génériques, et toutes les interfaces et implémentations de collections les utilisent largement. Les génériques nous permettent de fournir une collection avec un type Object qu'elle peut contenir, donc si vous ajoutez des éléments d'autres types, elle sera compilée comme une erreur. Cela évite ClassCastException au moment de l'exécution, puisque vous recevrez un message d'erreur au moment de la compilation. Les génériques rendent également le code plus propre, nous n'avons pas besoin d'utiliser des conversions explicites et l'opérateur instanceOf. Cela apporte également des avantages au runtime car aucune instruction de bytecode de type vérifié n'est générée.
Collection est l'interface racine du niveau collection. Une collection représente un ensemble d'objets qui sont ses éléments. La plateforme Java ne fournit aucune implémentation directe de cette interface.
Set est un ensemble qui ne peut pas contenir d'éléments en double. Cette interface modélise une abstraction mathématique d'ensembles et est utilisée pour représenter des ensembles, comme un jeu de cartes.
List est une collection ordonnée qui peut contenir des éléments répétés. Vous pouvez accéder à n’importe quel élément par son index. La liste ressemble plus à un tableau dont la longueur change dynamiquement.
Map est un objet qui mappe des clés à des valeurs. Une Map ne peut pas contenir de clés en double : chaque clé ne peut mapper qu'une seule valeur.
Certaines autres interfaces sont Queue, Dequeue, SortedSet, SortedMap et ListIterator.
L'interface de collection spécifie un ensemble d'objets, et les objets sont ses éléments. La manière dont ces éléments sont conservés est déterminée par la mise en œuvre spécifique de la Collection. Par exemple, certaines implémentations de Collection telles que List autorisent les éléments en double, tandis que d'autres telles que Set ne le font pas. De nombreuses implémentations de Collection ont une méthode de clonage publique. Cependant, cela n’a pas de sens de l’inclure dans toutes les implémentations de collections. C'est parce que Collection est une représentation abstraite. Ce qui compte, c'est la mise en œuvre.
La sémantique et les implications du clonage ou de la sérialisation entrent en jeu lorsqu'il s'agit d'implémentations spécifiques. Ainsi, l'implémentation doit décider comment la cloner ou la sérialiser, ou si elle peut être clonée ou sérialisée.
Favorisez le clonage et la sérialisation dans toutes les implémentations, ce qui conduit finalement à moins de flexibilité et à plus de restrictions. L'implémentation spécifique doit décider si elle peut être clonée et sérialisée.
Bien que l'interface Map et son implémentation fassent également partie du framework de collection, Map n'est pas une collection, et les collections ne sont pas des Maps. Par conséquent, cela n’a aucun sens que Map hérite de Collection et vice versa.
Si Map hérite de l'interface Collection, où vont les éléments ? La carte contient des paires clé-valeur, qui fournissent des méthodes pour extraire des collections de clés ou de listes de valeurs, mais elle ne correspond pas à la spécification « ensemble d'objets ».
Interface Iterator fournit une interface pour parcourir n'importe quelle collection. Nous pouvons utiliser la méthode itérateur pour obtenir une instance d'itérateur à partir d'une collection. Les itérateurs remplacent Enumeration dans le framework de collection Java. Les itérateurs permettent à l'appelant de supprimer des éléments pendant le processus d'itération.
L'énumération est deux fois plus rapide qu'Iterator et utilise moins de mémoire. Le dénombrement est très basique et répond à des besoins fondamentaux. Cependant, comparé à Enumeration, Iterator est plus sûr car il empêche les autres threads de modifier la collection pendant qu'une collection est parcourue.
Iterator remplace Enumeration dans Java Collections Framework. Les itérateurs permettent à l'appelant de supprimer des éléments d'une collection, contrairement à l'énumération. Les noms des méthodes itératives ont été améliorés pour rendre leurs fonctionnalités plus claires.
La sémantique n'est pas claire. On sait que le protocole Iterator ne garantit pas l'ordre d'itération. Notez cependant que ListIterator ne fournit pas d'opération d'ajout, qui garantit l'ordre d'itération.
Il peut être implémenté au niveau supérieur de l'Iterator actuel, mais il est rarement utilisé s'il est ajouté à l'interface, chaque héritage devra l'implémenter, ce qui n'a aucun sens.
(1) Nous pouvons utiliser Iterator pour parcourir les collections Set et List, tandis que ListIterator ne peut parcourir que List.
(2) Iterator ne peut traverser que vers l'avant, tandis que LIstIterator peut traverser dans les deux sens.
(3) ListIterator hérite de l'interface Iterator, puis ajoute quelques fonctions supplémentaires, telles que l'ajout d'un élément, le remplacement d'un élément et l'obtention de la position d'index de l'élément précédent ou suivant.
List<String> strList = new ArrayList<>(); //使用for-each循环 for(String obj : strList){ System.out.println(obj); } //using iterator Iterator<String> it = strList.iterator(); while(it.hasNext()){ String obj = it.next(); System.out.println(obj); }
L'utilisation d'itérateurs est plus sûre pour les threads car elle garantit que lorsque l'élément de collection actuellement parcouru est modifié, il lèvera une ConcurrentModificationException.
Chaque fois que nous essayons d'obtenir l'élément suivant, la propriété fail-fast Iterator vérifie tout changement dans la structure de collection actuelle. Si des modifications sont trouvées, il lève ConcurrentModificationException. Toutes les implémentations d'Iterator dans Collection sont conçues pour être rapides (à l'exception des classes de collection simultanées telles que ConcurrentHashMap et CopyOnWriteArrayList).
La propriété fail-fast d'Iterator fonctionne avec la collection actuelle, elle ne sera donc pas affectée par les modifications apportées à la collection. Toutes les classes de collection du package Java.util sont conçues pour être rapides, tandis que les classes de collection de java.util.concurrent sont sécurisées. Les itérateurs à sécurité intégrée lancent ConcurrentModificationException, tandis que les itérateurs à sécurité intégrée ne lancent jamais ConcurrentModificationException.
Lors de la traversée d'une collection, nous pouvons utiliser des classes de collection concurrentes pour éviter ConcurrentModificationException, par exemple en utilisant CopyOnWriteArrayList au lieu d'ArrayList.
L'interface Iterator définit des méthodes pour parcourir les collections, mais son implémentation relève de la responsabilité de la classe d'implémentation de la collection. Chaque classe de collection qui renvoie un itérateur pour le parcours possède sa propre classe interne d'implémentation d'itérateur.
Cela permet aux classes de collection de choisir si l'itérateur est rapide ou sécurisé. Par exemple, l'itérateur ArrayList est à sécurité intégrée et l'itérateur CopyOnWriteArrayList est à sécurité intégrée.
UnsupportedOperationException est une exception utilisée pour indiquer qu'une opération n'est pas prise en charge. Il a été largement utilisé dans les classes JDK. Dans le framework de collection, java.util.Collections.UnmodifiableCollection lèvera cette exception dans toutes les opérations d'ajout et de suppression.
HashMap stocke les paires clé-valeur dans l'implémentation de la classe interne Map.Entrystatic. HashMap utilise un algorithme de hachage et, dans les méthodes put et get, il utilise les méthodes hashCode() et equals(). Lorsque nous appelons la méthode put en passant la paire clé-valeur, HashMap utilise Key hashCode() et l'algorithme de hachage pour trouver l'index où la paire clé-valeur est stockée. L'entrée est stockée dans une LinkedList, donc si l'entrée existe, elle utilise la méthode equals() pour vérifier si la clé transmise existe déjà, si elle existe, elle écrase la valeur, si elle n'existe pas, elle crée une nouvelle entrée et puis l'enregistre. Lorsque nous appelons la méthode get en passant la clé, elle utilise à nouveau hashCode() pour trouver l'index dans le tableau, puis utilise la méthode equals() pour trouver l'entrée correcte, puis renvoie sa valeur. Les images ci-dessous expliquent les détails.
D'autres problèmes importants concernant HashMap sont la capacité, le facteur de charge et l'ajustement du seuil. La capacité initiale par défaut de HashMap est de 32 et le facteur de charge est de 0,75. Le seuil est le facteur de charge multiplié par la capacité Chaque fois que nous essayons d'ajouter une entrée, si la taille de la carte est supérieure au seuil, HashMap ressassera le contenu de la carte et utilisera la plus grande capacité. La capacité est toujours une puissance de 2, donc si vous savez que vous devez stocker un grand nombre de paires clé-valeur, comme la mise en cache des données extraites d'une base de données, c'est une bonne idée d'initialiser le HashMap avec les bonnes pratiques en matière de capacité et de facteur de charge.
HashMap utilise les méthodes hashCode() et equals() de l'objet Key pour déterminer l'index de la paire clé-valeur. Ces méthodes sont également utilisées lorsque nous essayons d'obtenir des valeurs de HashMap. Si ces méthodes ne sont pas implémentées correctement, auquel cas deux clés différentes peuvent produire la même sortie hashCode() et equals(), HashMap les considérera comme identiques et les écrasera au lieu de les stocker à des endroits différents. De même, toutes les classes de collection qui ne permettent pas le stockage de données en double utilisent hashCode() et equals() pour rechercher les doublons, il est donc très important de les implémenter correctement. L'implémentation de equals() et hashCode() doit suivre les règles suivantes :
(1) Si o1.equals(o2), alors o1.hashCode() == o2.hashCode() est toujours vrai.
(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。
我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点:
(1)如果类重写了equals()方法,它也应该重写hashCode()方法。
(2)类的所有实例需要遵循与equals()和hashCode()相关的规则。请参考之前提到的这些规则。
(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。
(4)用户自定义key类的最佳实践是使之为不可变的,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。
比如,我有一个类MyKey,在HashMap中使用它。
//传递给MyKey的name参数被用于equals()和hashCode()中 MyKey key = new MyKey('Pankaj'); //assume hashCode=1234 myHashMap.put(key, 'Value'); // 以下的代码会改变key的hashCode()和equals()值 key.setName('Amit'); //assume new hashCode=7890 //下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null myHashMap.get(new MyKey('Pankaj'));
那就是为何String和Integer被作为HashMap的key大量使用。
Map接口提供三个集合视图:
(1)Set keyset():返回map中包含的所有key的一个Set视图。集合是受map支持的,map的变化会在集合中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(2)Collection values():返回一个map中包含的所有value的一个Collection视图。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(3)Set44bf986331d5d9c3931140ad55669b0c> entrySet():返回一个map钟包含的所有映射的一个集合视图。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(1)HashMap允许key和value为null,而HashTable不允许。
(2)HashTable是同步的,而HashMap不是。所以HashMap适合单线程环境,HashTable适合多线程环境。
(3)在Java1.4中引入了LinkedHashMap,HashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,但是HashTable不是这样的,它的顺序是不可预知的。
(4)HashMap提供对key的Set进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
(5)HashTable被认为是个遗留的类,如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap。
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是固定的。
Array ne fournit pas autant de fonctions qu'ArrayList, telles que addAll, removeAll et iterator. Bien qu'ArrayList soit clairement le meilleur choix, il arrive parfois que Array soit meilleur.
(1) Si la taille de la liste a été précisée, il s'agit la plupart du temps de les stocker et de les parcourir.
(2) Pour parcourir les types de données de base, bien que les collections utilisent la boxe automatique pour faciliter la tâche de codage, travailler sur des listes de types de base d'une taille spécifiée peut devenir lent.
(3) Si vous souhaitez utiliser un tableau multidimensionnel, il est plus facile d'utiliser [][] que List658a6c8c8956eca63df9b922e1300853>.
ArrayList et LinkedList implémentent l'interface List, mais il existe quelques différences entre eux.
(1) ArrayList est une structure de données basée sur un index pris en charge par Array, elle fournit donc un accès aléatoire aux éléments d'une complexité O(1), mais LinkedList stocke une série de données de nœud, chaque nœud est connecté au nœud précédent et suivant. Par conséquent, bien qu'il existe une méthode pour obtenir des éléments à l'aide d'index, l'implémentation interne consiste à parcourir depuis le point de départ, jusqu'au nœud indexé, puis à renvoyer l'élément. La complexité temporelle est O(n), ce qui est plus lent que ArrayList.
(2) Par rapport à ArrayList, l'insertion, l'ajout et la suppression d'un élément dans LinkedList seront plus rapides, car lorsqu'un élément est inséré au milieu, cela n'implique pas de changer la taille du tableau ni de mettre à jour l'index .
(3) LinkedList consomme plus de mémoire que ArrayList car chaque nœud de LinkedList stocke les références aux nœuds précédents et suivants.
Les classes ArrayList, HashMap, TreeMap et HashTable offrent un accès aléatoire aux éléments.
java.util.EnumSet est une implémentation de collection utilisant des types d'énumération. Lorsqu'une collection est créée, tous les éléments de la collection d'énumération doivent provenir d'un seul type d'énumération spécifié, explicitement ou implicitement. EnumSet n'est pas synchronisé et n'autorise pas les éléments nuls. Il fournit également des méthodes utiles, telles que copyOf(Collection c), of(E first,E...rest) et complémentOf(EnumSet s).
Vector, HashTable, Properties et Stack sont des classes de synchronisation, elles sont donc thread-safe et peuvent être utilisées dans un environnement multithread. L'API de concurrence Java 1.5 inclut certaines classes de collection qui permettent des modifications pendant l'itération et, comme elles fonctionnent toutes sur des clones de la collection, elles sont en sécurité dans un environnement multithread.
Le package concurrent Java 1.5 (java.util.concurrent) contient des classes de collection thread-safe qui permettent de modifier les collections pendant l'itération. Les itérateurs sont conçus pour être rapides et lanceront une exception ConcurrentModificationException. Certaines classes sont : CopyOnWriteArrayList, ConcurrentHashMap, CopyOnWriteArraySet.
Java.util.concurrent.BlockingQueue est une file d'attente Lors de la récupération ou de la suppression d'un élément, il attendra que la file d'attente devienne non vide lors de l'ajout d'un élément, il attendra que la file d'attente devienne non vide ; -vide. Espace disponible. L'interface BlockingQueue fait partie du framework de collection Java et est principalement utilisée pour implémenter le modèle producteur-consommateur. Nous n'avons pas à nous soucier d'attendre que le producteur ait de l'espace disponible ou que le consommateur ait des objets disponibles, car tout est géré dans la classe d'implémentation BlockingQueue. Java fournit des implémentations centralisées de BlockingQueue, telles que ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue, etc.
Les piles et les files d'attente sont utilisées pour pré-stocker les données. java.util.Queue est une interface et sa classe d'implémentation se trouve dans le package de concurrence Java. Les files d'attente permettent de récupérer les éléments selon le principe premier entré, premier sorti (FIFO), mais ce n'est pas toujours le cas. L'interface Deque permet de récupérer des éléments aux deux extrémités.
Une pile est similaire à une file d'attente, mais elle permet la récupération des éléments par le dernier entré, premier sorti (LIFO).
Stack est une classe qui s'étend de Vector, et Queue est une interface.
Java.util.Collections est une classe utilitaire contenant uniquement des méthodes statiques, qui opèrent sur ou renvoient des collections. Il contient des algorithmes polymorphes qui opèrent sur les collections, renvoient une nouvelle collection soutenue par la collection spécifiée et quelques autres éléments. Cette classe contient des méthodes pour les algorithmes de structure de collecte, tels que la recherche binaire, le tri, la lecture aléatoire et l'ordre inverse.
Si nous voulons utiliser la méthode de tri Array ou Collection, nous devons implémenter l'interface Comparable fournie par Java dans une classe personnalisée. L'interface Comparable possède la méthode compareTo(T OBJ), qui est utilisée par la méthode de tri. Nous devons remplacer cette méthode afin qu'elle renvoie un entier négatif, 0, ou un entier positif si l'objet "this" est plus petit, égal ou plus grand que l'argument d'objet transmis. Cependant, dans la plupart des cas pratiques, nous souhaitons trier en fonction de différents paramètres. Par exemple, en tant que PDG, je souhaite trier les employés en fonction de leur salaire, et un RH souhaite les trier en fonction de leur âge. C'est la situation dans laquelle nous devons utiliser l'interface Comparator, car l'implémentation de la méthode Comparable.compareTo(Object o) ne peut trier qu'en fonction d'un seul champ, et nous ne pouvons pas sélectionner le champ en fonction des besoins de tri des objets. L'implémentation de la méthode compare(Object o1, Object o2) de l'interface Comparator doit passer deux paramètres d'objet si le premier paramètre est inférieur au second, un entier négatif est renvoyé si le premier paramètre est égal au second ; 0 est renvoyé ; si le premier paramètre est inférieur au deuxième paramètre, un entier négatif est renvoyé. Si un est supérieur au second, un entier positif est renvoyé.
Les interfaces Comparable et Comparator sont utilisées pour trier des collections d'objets ou des tableaux. L'interface Comparable est utilisée pour fournir un classement naturel des objets, et nous pouvons l'utiliser pour fournir un classement basé sur une logique unique.
L'interface Comparator est utilisée pour fournir différents algorithmes de tri. Nous pouvons choisir le Comparator que nous devons utiliser pour trier une collection d'objets donnée.
Si nous devons trier un tableau d'objets, nous pouvons utiliser la méthode Arrays.sort(). Si nous devons trier une liste d’objets, nous pouvons utiliser la méthode Collection.sort(). Les deux classes ont une méthode surchargée sort() pour le tri naturel (en utilisant Comparable) ou le tri basé sur des critères (en utilisant Comparator). Les collections utilisent en interne la méthode tri de tableau, donc les deux ont les mêmes performances, sauf que les collections prennent du temps pour convertir la liste en tableau.
Nous pouvons utiliser la méthode Collections.unmodifiableCollection(Collection c) pour créer une collection en lecture seule avant de la passer en paramètre, ce qui garantira que toute opération modifiant la collection lèvera une UnsupportedOperationException.
Nous pouvons utiliser Collections.synchronizedCollection(Collection c) pour obtenir une collection synchronisée (thread-safe) basée sur la collection spécifiée.
Le framework de collection Java fournit des implémentations d'algorithmes courantes, telles que le tri et la recherche. La classe Collections contient des implémentations de ces méthodes. La plupart des algorithmes fonctionnent sur des Listes, mais certains sont disponibles pour tous types de collections. Certains algorithmes incluent le tri, la recherche, le mélange et le max-min.
Un O majuscule décrit les performances d'un algorithme en termes d'une série d'éléments dans la structure des données. La classe Collection est la structure de données réelle, et nous choisissons généralement une implémentation de collection basée sur le temps, la mémoire et les performances, avec un O majuscule. Par exemple : Exemple 1 : le get(index i) d'ArrayList est une opération à temps constant, qui ne dépend pas du nombre d'éléments dans la liste. Sa performance est donc O(1). Exemple 2 : la performance d'une recherche linéaire dans un tableau ou une liste est O(n) car nous devons parcourir tous les éléments pour trouver l'élément requis.
(1) Choisissez le type de collecte approprié en fonction de vos besoins. Par exemple, si la taille est spécifiée, nous utiliserons Array au lieu de ArrayList. Si nous voulons parcourir une carte en fonction de l'ordre d'insertion, nous devons utiliser TreeMap. Si nous ne voulons pas répéter, nous devons utiliser Set.
(2) Certaines classes de collection permettent de spécifier la capacité initiale, donc si on peut estimer le nombre d'éléments stockés, on peut l'utiliser et éviter de ressasser ou de redimensionner.
(3) Basé sur une programmation d'interface plutôt que sur une programmation basée sur l'implémentation, cela nous permet de modifier facilement l'implémentation ultérieurement.
(4) Utilisez toujours des génériques de type sécurisé pour éviter ClassCastException au moment de l'exécution.
(5) Utilisez la classe immuable fournie par le JDK comme clé de la Map pour éviter d'implémenter vous-même hashCode() et equals().
(6) Utilisez la classe d'outils Collections autant que possible, ou obtenez une collection en lecture seule, synchronisée ou vide au lieu d'écrire votre propre implémentation. Il permettra la réutilisabilité du code et aura une meilleure stabilité et maintenabilité.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!