Fragen zu Java Collections

Fragen in Vorstellungsgesprächen bei Java Collections

1. Einführung

Java-Sammlungen sind ein Thema, das häufig in technischen Interviews für Java-Entwickler zur Sprache kommt. In diesem Artikel werden einige wichtige Fragen besprochen, die am häufigsten gestellt werden und möglicherweise schwierig zu beantworten sind.

2. Fragen

Q1. Beschreiben der Hierarchie der Auflistungstypen. Was sind die Hauptschnittstellen und was sind die Unterschiede zwischen ihnen?

DieIterable-Schnittstelle repräsentiert jede Sammlung, die mit derfor-each-Schleife iteriert werden kann. DieCollection-Schnittstelle erbt vonIterable und fügt generische Methoden hinzu, um zu überprüfen, ob sich ein Element in einer Sammlung befindet, Elemente zur Sammlung hinzuzufügen und daraus zu entfernen, seine Größe zu bestimmen usw.

Die SchnittstellenList,Set undQueue erben von der SchnittstelleCollection.

List ist eine geordnete Sammlung, auf deren Elemente über ihren Index in der Liste zugegriffen werden kann.

Set ist eine ungeordnete Sammlung mit unterschiedlichen Elementen, ähnlich dem mathematischen Begriff einer Menge.

Queue ist eine Sammlung mit zusätzlichen Methoden zum Hinzufügen, Entfernen und Untersuchen von Elementen, die zum Halten von Elementen vor der Verarbeitung nützlich sind.

Die Schnittstelle vonMapist ebenfalls Teil des Sammlungsframeworks, erweitert jedoch nichtCollection. Dies ist beabsichtigt, um den Unterschied zwischen Sammlungen und Zuordnungen hervorzuheben, die unter einer gemeinsamen Abstraktion nur schwer zu erfassen sind. DieMap-Schnittstelle repräsentiert eine Schlüsselwertdatenstruktur mit eindeutigen Schlüsseln und nicht mehr als einem Wert für jeden Schlüssel.

 

Q2. Beschreiben verschiedener Implementierungen der Kartenschnittstelle und ihrer Anwendungsfallunterschiede.

Eine der am häufigsten verwendeten Implementierungen derMap-Schnittstelle istHashMap. Es ist eine typische Hash-Map-Datenstruktur, die den Zugriff auf Elemente in konstanter Zeit oder O (1), aberdoes not preserve order and is not thread-safe ermöglicht.

Um die Einfügereihenfolge von Elementen beizubehalten, können Sie die KlasseLinkedHashMap verwenden, die dieHashMap erweitert und die Elemente zusätzlich mit vorhersehbarem Overhead in eine verknüpfte Liste einbindet.

Die KlasseTreeMappeichert ihre Elemente in einer rot-schwarzen Baumstruktur, die den Zugriff auf Elemente in logarithmischer Zeit oder O (log (n)) ermöglicht. Es ist in den meisten Fällen langsamer alsHashMap, ermöglicht jedoch, die Elemente gemäß einigenComparator in der richtigen Reihenfolge zu halten.

ConcurrentHashMap ist eine thread-sichere Implementierung einer Hash-Map. Es bietet die vollständige Parallelität von Abrufen (da die Operationgetkeine Sperrung beinhaltet) und eine hohe erwartete Parallelität von Aktualisierungen.

Die KlasseHashtablebefindet sich seit Version 1.0 in Java. Es ist nicht veraltet, wird aber meistens als veraltet angesehen. Es ist eine thread-sichere Hash-Map, aber im Gegensatz zuConcurrentHashMap sind alle Methoden einfachsynchronized, was bedeutet, dass alle Operationen auf diesem Map-Block sogar das Abrufen unabhängiger Werte blockieren.

 

Q3. Erklären Sie den Unterschied zwischen Linkedlist und Arraylist.

ArrayList ist eine Implementierung derList-Schnittstelle, die auf einem Array basiert. ArrayList behandelt intern die Größenänderung dieses Arrays, wenn die Elemente hinzugefügt oder entfernt werden. Sie können über ihren Index im Array in konstanter Zeit auf seine Elemente zugreifen. Durch das Einfügen oder Entfernen eines Elements werden jedoch alle nachfolgenden Elemente verschoben, die möglicherweise langsam sind, wenn das Array sehr groß ist und sich das eingefügte oder entfernte Element am Anfang der Liste befindet.

LinkedList ist eine doppelt verknüpfte Liste: Einzelne Elemente werden inNode-Objekten eingefügt, die Verweise auf vorherige und nächsteNode enthalten. Diese Implementierung erscheint möglicherweise effizienter alsArrayList, wenn Sie viele Einfügungen oder Löschungen in verschiedenen Teilen der Liste haben, insbesondere wenn die Liste groß ist.

In den meisten Fällen übertrifft jedochArrayListLinkedList. Sogar Elemente, die sich inArrayList verschieben, obwohl sie eine O (n) -Operation sind, werden als sehr schnellerSystem.arraycopy()-Aufruf implementiert. Es kann sogar schneller erscheinen als die O (1) -Einfügung vonLinkedList, bei der einNode-Objekt instanziiert und mehrere Referenzen unter der Haube aktualisiert werden müssen. LinkedList können aufgrund der Erstellung mehrerer kleinerNode-Objekte auch einen großen Speicheraufwand haben.

 

Q4. Was ist der Unterschied zwischen Hashset und Treeset?

Sowohl die KlassenHashSet als auchTreeSet implementieren dieSet-Schnittstelle und repräsentieren Sätze unterschiedlicher Elemente. Zusätzlich implementiertTreeSet dieNavigableSet-Schnittstelle. Diese Schnittstelle definiert Methoden, die die Reihenfolge der Elemente nutzen.

HashSet basiert intern aufHashMap, undTreeSet wird von einerTreeMap-Instanz unterstützt, die ihre Eigenschaften definiert:HashSet enthält keine bestimmten Elemente Bestellung. Die Iteration über die Elemente inHashSet erzeugt sie in einer gemischten Reihenfolge. TreeSet erzeugt andererseits Elemente in der Reihenfolge einiger vordefinierterComparator.

 

Q5. Wie ist Hashmap in Java implementiert? Wie verwendet die Implementierung Hashcode und gleicht Methoden von Objekten ab? Was ist die zeitliche Komplexität des Platzierens und Erhaltens eines Elements aus einer solchen Struktur?

Die KlasseHashMap repräsentiert eine typische Hash-Map-Datenstruktur mit bestimmten Entwurfsoptionen.

DasHashMap wird von einem anpassbaren Array mit einer Zweierpotenzgröße unterstützt. Wenn das Element zu einemHashMap hinzugefügt wird, wird zuerst seinhashCode berechnet (einint-Wert). Dann wird eine bestimmte Anzahl von niedrigeren Bits dieses Werts als Array-Index verwendet. Dieser Index verweist direkt auf die Zelle des Arrays (als Bucket bezeichnet), in der dieses Schlüssel-Wert-Paar platziert werden soll. Der Zugriff auf ein Element über seinen Index in einem Array ist eine sehr schnelle O (1) -Operation, die das Hauptmerkmal einer Hash-Map-Struktur ist.

AhashCode ist jedoch nicht eindeutig, und selbst für verschiedenehashCodes erhalten wir möglicherweise dieselbe Array-Position. Dies nennt man eine Kollision. Es gibt mehr als eine Möglichkeit, Kollisionen in den Hash-Kartendatenstrukturen aufzulösen. InHashMap von Java bezieht sich jeder Bucket nicht auf ein einzelnes Objekt, sondern auf einen rot-schwarzen Baum aller Objekte, die in diesem Bucket gelandet sind (vor Java 8 war dies eine verknüpfte Liste).

Wenn alsoHashMap den Bucket für einen Schlüssel bestimmt hat, muss es diesen Baum durchlaufen, um das Schlüssel-Wert-Paar an seine Stelle zu setzen. Wenn ein Paar mit einem solchen Schlüssel bereits im Eimer vorhanden ist, wird er durch einen neuen ersetzt.

Um das Objekt anhand seines Schlüssels abzurufen, müssen dieHashMap erneut diehashCode für den Schlüssel berechnen, den entsprechenden Bucket suchen, den Baum durchlaufen,equals auf den Schlüsseln im Baum aufrufen und suchen der passende.

HashMap hat eine O (1) -Komplexität oder eine zeitkonstante Komplexität beim Setzen und Abrufen der Elemente. Natürlich können viele Kollisionen die Leistung im schlimmsten Fall auf 0 (log (n)) Zeitkomplexität verschlechtern, wenn alle Elemente in einem einzigen Bucket landen. Dies wird normalerweise gelöst, indem eine gute Hash-Funktion mit einer gleichmäßigen Verteilung bereitgestellt wird.

Wenn das interne Array vonHashMapgefüllt ist (mehr dazu in der nächsten Frage), wird die Größe automatisch auf die doppelte Größe geändert. Dieser Vorgang führt zu einem kostspieligen Aufbereiten (Wiederherstellen interner Datenstrukturen). Sie sollten daher die Größe IhrerHashMapim Voraus planen.

 

Q6. Was ist der Zweck der anfänglichen Kapazitäts- und Lastfaktorparameter einer Hashmap? Was sind ihre Standardwerte?

DasinitialCapacity-Argument desHashMap-Konstruktors wirkt sich auf die Größe der internen Datenstruktur desHashMap aus, aber das Nachdenken über die tatsächliche Größe einer Karte ist etwas schwierig. Die interne Datenstruktur vonHashMapist ein Array mit der Zweierpotenzgröße. Der Argumentwert voninitialCapacitywird also auf die nächste Zweierpotenz erhöht (wenn Sie ihn beispielsweise auf 10 setzen, beträgt die tatsächliche Größe des internen Arrays 16).

Der Lastfaktor von aHashMap ist das Verhältnis der Elementanzahl geteilt durch die Schaufelanzahl (d. H. interne Arraygröße). Wenn beispielsweise ein 16-BucketHashMap 12 Elemente enthält, beträgt sein Lastfaktor 12/16 = 0,75. Ein hoher Lastfaktor bedeutet eine Menge Kollisionen, was wiederum bedeutet, dass die Größe der Karte auf die nächste Zweierpotenz geändert werden sollte. Das ArgumentloadFactorist also ein Maximalwert des Lastfaktors einer Karte. Wenn die Karte diesen Auslastungsfaktor erreicht, ändert sie die Größe ihres internen Arrays auf den nächsten Zweierpotenzwert.

initialCapacity ist standardmäßig 16 und loadFactor ist standardmäßig 0,75. Sie können also 12 Elemente inHashMap einfügen, die mit dem Standardkonstruktor instanziiert wurden, und die Größe wird nicht geändert. Gleiches gilt fürHashSet, das intern von einerHashMap-Instanz unterstützt wird.

Folglich ist es nicht trivial,initialCapacity zu finden, die Ihren Anforderungen entsprechen. Aus diesem Grund verfügt die Guava-Bibliothek über MethodenMaps.newHashMapWithExpectedSize() undSets.newHashSetWithExpectedSize(), mit denen SieHashMap oderHashSet erstellen können, die die erwartete Anzahl von Elementen ohne Größenänderung enthalten können.

===

Q7. Beschreiben von Spezialsammlungen für Enums. Was sind die Vorteile ihrer Implementierung im Vergleich zu regulären Sammlungen?

EnumSet undEnumMap sind spezielle Implementierungen der SchnittstellenSet undMap entsprechend. Sie sollten diese Implementierungen immer verwenden, wenn Sie mit Aufzählungen arbeiten, da sie sehr effizient sind.

EinEnumSet ist nur ein Bitvektor mit "Einsen" an den Positionen, die den Ordnungswerten der in der Menge vorhandenen Aufzählungen entsprechen. Um zu überprüfen, ob sich ein Aufzählungswert in der Menge befindet, muss die Implementierung lediglich prüfen, ob das entsprechende Bit im Vektor eine Eins ist, was eine sehr einfache Operation ist. In ähnlicher Weise ist einEnumMap ein Array, auf das mit dem Ordnungswert von enum als Index zugegriffen wird. Im Fall vonEnumMap müssen keine Hash-Codes berechnet oder Kollisionen aufgelöst werden.

 

Q8. Was ist der Unterschied zwischen ausfallsicheren und ausfallsicheren Iteratoren?

Iteratoren für verschiedene Sammlungen sind entweder ausfallsicher oder ausfallsicher, je nachdem, wie sie auf gleichzeitige Änderungen reagieren. Die gleichzeitige Änderung ist nicht nur eine Änderung der Auflistung eines anderen Threads, sondern auch eine Änderung desselben Threads, sondern die Verwendung eines anderen Iterators oder die direkte Änderung der Auflistung.

Fail-fast Iteratoren (die vonHashMap,ArrayList und anderen nicht threadsicheren Sammlungen zurückgegeben werden) durchlaufen die interne Datenstruktur der Sammlung und werfenConcurrentModificationException, sobald Sie erkennen eine gleichzeitige Änderung.

Fail-safe Iteratoren (die von thread-sicheren Sammlungen wieConcurrentHashMap,CopyOnWriteArrayList zurückgegeben werden) erstellen eine Kopie der Struktur, auf der sie iterieren. Sie gewährleisten die Sicherheit vor gleichzeitigen Änderungen. Zu den Nachteilen gehören ein übermäßiger Speicherverbrauch und die Iteration möglicherweise veralteter Daten, falls die Sammlung geändert wurde.

 

Q9. Wie können Sie Vergleichs- und Vergleichsschnittstellen verwenden, um Sammlungen zu sortieren?

DieComparable-Schnittstelle ist eine Schnittstelle für Objekte, die in einer bestimmten Reihenfolge verglichen werden können. Die einzige Methode istcompareTo, die mit zwei Werten arbeitet: dem Objekt selbst und dem Argumentobjekt desselben Typs. Beispielsweise implementierenInteger,Long und andere numerische Typen diese Schnittstelle. String implementiert auch diese Schnittstelle, und die MethodecompareTo vergleicht Zeichenfolgen in lexikografischer Reihenfolge.

DieComparable-Schnittstelle ermöglicht das Sortieren von Listen entsprechender Objekte mit derCollections.sort()-Methode und das Aufrechterhalten der Iterationsreihenfolge in Sammlungen, dieSortedSet undSortedMap implementieren. Wenn Ihre Objekte mithilfe einer Logik sortiert werden können, sollten sie dieComparable-Schnittstelle implementieren.

DieComparable-Schnittstelle wird normalerweise unter Verwendung einer natürlichen Reihenfolge der Elemente implementiert. Zum Beispiel werden alleInteger-Zahlen von kleineren zu größeren Werten geordnet. Manchmal möchten Sie jedoch möglicherweise eine andere Sortierung implementieren, beispielsweise um die Nummern in absteigender Reihenfolge zu sortieren. DieComparator-Schnittstelle kann hier helfen.

Die Klasse der Objekte, die Sie sortieren möchten, muss diese Schnittstelle nicht implementieren. Sie erstellen einfach eine implementierende Klasse und definieren die Methodecompare, die zwei Objekte empfängt und entscheidet, wie sie sortiert werden sollen. Sie können dann die Instanz dieser Klasse verwenden, um die natürliche Reihenfolge der InstanzenCollections.sort() oder der InstanzenSortedSet undSortedMapzu überschreiben.

Da dieComparator-Schnittstelle eine funktionale Schnittstelle ist, können Sie sie wie im folgenden Beispiel durch einen Lambda-Ausdruck ersetzen. Es zeigt das Ordnen einer Liste unter Verwendung einer natürlichen Reihenfolge (IntegerComparable-Schnittstelle) und unter Verwendung eines benutzerdefinierten Iterators (Comparator<Integer>-Schnittstelle).

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1);
assertEquals(new Integer(1), list1.get(0));

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1, (a, b) -> b - a);
assertEquals(new Integer(5), list1.get(0));