89. ArrayList 与 LinkedList 有何不同?
这是最常见的问题之一,还有关于 HashMap 内部结构的问题。没有它,面试就不完整,所以你的答案应该很容易脱口而出。除了明显的差异(它们有不同的名称)之外,它们的内部结构也有所不同。之前,我们讨论了ArrayList和LinkedList的内部结构,因此我不会深入讨论它们的实现细节。我只是提醒您,ArrayList是使用内部数组实现的,其大小根据以下公式动态增加:
<size of the current array> * 3 / 2 + 1
此外, LinkedList 的实现使用内部双向链表,即每个元素都有对前一个和下一个元素的引用,但列表开头和结尾的元素除外。面试官喜欢问这样的问题:“ ArrayList和LinkedList哪个更好?” 希望能抓住你。毕竟,如果你说其中一个更好,那么你就给出了错误的答案。 相反,您应该澄清您所讨论的具体情况:通过索引访问元素或插入列表中间。然后,根据他们的回答,您可以解释哪一个更好。我之前描述了ArrayList和LinkedList在每种情况下如何工作。让我们通过将它们放在一行中进行比较来总结这一点: 添加元素(add)
-
如果未指定索引,则新项目将自动添加到两种列表的末尾。在LinkedList中,新元素将成为新尾部(只会重写一对引用,因此算法复杂度为O(1))。
add 方法将一个元素添加到数组中的最后一个空单元格 ( O(1) )。
-
通过索引添加项目通常意味着将其插入列表中间的某个位置。在LinkedList中,该方法将首先通过迭代尾部和头部的元素来搜索所需的位置 ( O(n/2) ),然后通过覆盖两侧元素的引用来插入值插入新元素 ( O(1) )。此操作的总体算法复杂度将为O(n/2)。
在相同的情况下(按索引添加),ArrayList找到所需的位置(O(1)),然后将位于右侧的所有元素(包括已存储在指定索引处的元素)向右移动一位(这可能需要创建一个新的内部数组并将元素复制到其中)(O(n/2))。总体复杂度为O(n/2)。
-
将元素添加到LinkedList的开头类似于将元素添加到末尾:新元素成为新头 ( O(1) )。但对于 ArrayList,该操作需要将所有元素移动到右侧 ( O(n) )。
90. ArrayList 与 HashSet 有何不同?
如果我们可以逐个操作地比较ArrayList和LinkedList以确定哪一个更好,那么我们会发现在ArrayList和HashSet之间进行这样的比较就没那么容易了,因为它们是完全不同的集合。你可以将一种甜点与另一种甜点进行比较,但比较甜点和咸味菜肴是一项挑战——它们截然不同。尽管如此,我还是会尝试指出它们之间的一些差异:-
ArrayList实现了List接口,而HashSet实现了Set接口。
-
ArrayList允许您通过索引访问元素:get操作的算法复杂度为O(1),但HashSet只允许您通过迭代访问所需的元素,这会产生从O(1)到O(n) 的算法复杂度。
-
ArrayList允许重复元素。在HashSet中,所有元素都是唯一的:任何添加 HashSet 中已存在的元素的尝试都将失败(通过哈希码检查重复项,因此该集合的名称也由此而来)。
-
ArrayList是使用内部数组实现的,而HashSet是使用内部HashMap实现的。
-
ArrayList维护元素的插入顺序,但HashSet是无序集合,不维护元素的顺序。
-
ArrayList允许任意数量的空值,但你只能向HashSet添加一个空值(毕竟元素必须是唯一的)。
91. 为什么Java有这么多不同的动态数组实现?
这更多的是一个哲学问题。我们还可以问为什么他们会想出这么多新奇的技术?为了方便。对于大量动态数组实现也是如此。它们都不能被称为最好或理想的实现。每种情况都有其优点。我们的工作是了解它们的差异以及它们的优点/缺点,以便能够使用最适合任何特定情况的集合。92. 为什么Java有这么多不同的键值存储实现?
这里的情况与动态数组实现相同。绝对没有一个是普遍优于其他的:每个都有优点和缺点。当然,我们必须充分利用他们的优势。 示例: concurrent 包有很多多线程类,有自己的Concurrent集合。在多线程环境中处理数据时,ConcurrentHashMap 类在安全性方面比标准 HashMap 具有优势,但这是以性能较慢为代价的。在任何情况下都不是最佳选择的实现将逐渐停止使用。 例如: Hashtable原本是打算成为线程安全的HashMap,但现在已经被遗忘并不再使用了,因为ConcurrentHashMap在多线程环境中工作时 甚至比Hashtable更好。93. 如何对元素集合进行排序?
首先要说的是,表示集合元素的类必须实现Comparable接口,该接口由compareTo方法组成。或者您需要一个实现Comparator接口的类,包括其比较方法。这两种方法都指示如何比较给定类型的对象。这在排序时至关重要,因为排序算法需要了解使用什么原理来比较元素。这主要是通过直接在要排序的类中实现Comparable来完成的。使用比较器不太常见。假设您正在使用某个库中的类,并且它没有实现Comparable,但您需要对其对象的集合进行排序。由于您无法更改此类的代码(除非扩展它),因此您可以编写Comparator的实现来指示如何比较该类的对象。还有一个例子。如果您需要以不同的方式对同一类型的对象进行排序,那么您可以编写多个Comparator实现以在不同的情况下使用。通常,许多开箱即用的类(例如String)已经实现了Comparable接口。这意味着您无需担心如何比较这些类。您可以继续使用它们。 第一个也是最明显的方法是使用TreeSet或TreeMap类。这些类根据类元素实现的比较器按排序顺序存储元素。不要忘记TreeMap对键进行排序,而不是对值进行排序。如果您使用Comparator而不是Comparable,那么您需要在创建集合时 将Comparator对象传递给集合的构造函数:
TreeSet treeSet = new TreeSet(customComparator);
但是如果您有不同类型的集合怎么办?你如何排序?在这种情况下,Collections实用程序类的第二种方法 — sort()方法 — 是合适的。该方法是静态的,因此您所需要做的就是在前面添加类的名称,然后传入要排序的列表。例如:
Collections.sort(someList);
如果您使用Comparator而不是Comparable 的实现,那么您需要将其作为第二个参数传递:
Collections.sort(someList, customComparator);
此操作将更改传递列表中元素的内部顺序:将使用比较器对列表进行排序。请注意,传递的列表必须是可变的,否则该方法将失败并抛出UnsupportedOperationException。第三种选择是使用Stream类的排序方法,该方法对集合的元素进行排序。如果我们使用Comparable:
someList = someList.stream().sorted().collect(Collectors.toList());
如果我们使用比较器:
someList = someList.stream().sorted(customComparator).collect(Collectors.toList());
第四种方式是手动实现排序算法,例如冒泡排序
或归并排序
。
对象类。equals() 和 hashCode()
94. 简要描述Java中的Object类。
在回顾的第二部分中,我们已经讨论了 Object类的方法。这里我要提醒大家,Object类是Java中每个类的祖先。它有 11 个方法,依次被所有类继承。95. Java中equals()和hashCode()的用途是什么?
hashCode()是Object类的一个方法,被所有类继承。它的工作是生成一个代表特定对象的数字。可以在HashMap中找到此方法的实际示例,其中对键对象调用该方法以获取本地哈希码,这将确定键值对将存储在哪个存储桶(内部数组的单元格)中。此外,该方法一般用在equals()方法中,作为其识别对象的主要方式之一。 equals()是Object类的一个方法,其作用是比较对象并确定它们是否相等。这个方法用在我们需要比较对象的任何地方,因为标准的==比较运算符不适合对象,因为它只比较对象引用。96. 请告诉我们 Java 中 equals() 和 hashCode() 之间的约定?
首先,我要说的是,为了使equals()和hashCode()方法正常工作,必须正确重写它们。他们的新实现必须遵循以下规则:- equals返回true 的相同对象必须具有相同的哈希码。
- 具有相同哈希码的对象不一定相等。
GO TO FULL VERSION