
收藏
84. 告诉我们关于迭代器及其使用方法
集合是所有 Java 开发人员面试中最喜欢的话题。当回答有关集合层次结构的问题时,考生经常说它是从集合接口开始的。但事实并非如此。上面还有另一个接口:Iterable。该接口由iterator()方法组成,该方法允许您访问当前集合的Iterator对象。这个Iterator对象到底是什么?Iterator对象提供了在集合中移动并迭代其元素的能力,并且用户不需要知道集合的具体实现细节。换句话说,它是一种指向集合元素的指针,就好像它正在窥视其中一个元素一样。迭代器具有以下方法:-
hasNext() —如果迭代有另一个元素,则返回true (此方法让您知道何时到达集合的末尾);
-
next() — 返回迭代中的下一项。如果没有,则抛出NoSuchElementException 。这意味着在使用此方法之前,最好使用hasNext()方法来确保下一个元素存在;
-
remove() — 从集合中删除使用next()方法接收到的最后一个元素。如果next()从未被调用过,那么调用remove()将导致抛出IllegalStateException ;
-
forEachRemaining(<Consumer>) — 对集合的每个元素执行传递的操作(此方法出现在 Java 8 中)。
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
控制台将显示以下内容:
iterator.forEachRemaining(x -> System.out.print(x));
但是一旦我们这样做了,迭代器就不再适合进一步使用:它已经遍历了整个列表,而普通的迭代器没有向后迭代的方法。这使得我们可以很好地讨论LinkedList,特别是它的listIterator()方法,该方法返回增强类型的迭代器:ListIterator。除了常规(标准)迭代器的方法之外,这种迭代器还具有以下功能:
-
add(<Element>) — 将新元素添加到列表中;
-
hasPrevious() —如果有一个元素位于下一个元素之前(如果有前一个元素),则返回true ;
-
nextIndex() — 返回下一个元素的索引;
-
previous() — 返回上一个元素(下一个元素之前的元素);
-
previousIndex返回前一个元素的索引。
-
set(<Element>) — 替换next()或previous()返回的最后一个元素。

85. Java 集合框架中存在什么集合层次结构?
Java 中有两种集合层次结构。 第一个层次结构是 Collection 层次结构,其结构如下:
-
集合是描述集合的接口,集合是包含无序唯一(非重复)元素的数据结构。该接口有一些标准实现:TreeSet、HashSet和LinkedHashSet。
-
列表是一个描述存储有序对象序列的数据结构的接口。列表中的对象可以通过列表中的索引插入和删除(类似于数组,但可以动态调整大小)。该接口还有一些标准实现:ArrayList、Vector(已弃用且未实际使用)和LinkedList。
-
队列是一个接口,描述将项目存储在先进先出 (FIFO)队列中的数据结构。该接口有以下标准实现:LinkedList(没错,它也实现了Queue)和PriotityQueue。


86.ArrayList的内部结构是什么?
ArrayList就像一个数组,但它可以动态扩展。这意味着什么?在底层,ArrayList使用普通数组,即将其元素存储在默认大小为 10 个单元格的内部数组中。一旦内部数组满了,就会创建一个新数组。新数组的大小由以下公式确定:<size of the current array> * 3 / 2 + 1
因此,如果数组的大小为 10,则新数组的大小将为:10 * 3 / 2 + 1 = 16。然后使用内置函数将原始(旧)数组中的所有值复制到其中System.arraycopy()方法,原数组被删除。简而言之,这就是ArrayList实现动态调整大小的方式。让我们考虑一下最流行的ArrayList方法: 1. add(<Element>) — 在首先检查数组中是否有可用单元之后,将一个元素添加到数组末尾(最后一个空单元中)。如果没有,则创建一个新数组,并将元素复制到其中。该操作的时间复杂度为O(1)。还有一个类似的add(<Index>, <Elelement>)方法。它不会将元素添加到列表(数组)的末尾,而是添加到作为参数传入的索引指示的特定单元格中。在这种情况下,时间复杂度将根据您添加的位置而有所不同:
- 如果添加接近列表的开头,那么时间复杂度将接近 O(N),因为位于新元素右侧的所有元素都必须向右移动一个单元格;
- 如果将元素插入到中间,那么它将是 O(N/2),因为我们只需要将一半的列表项向右移动一个单元格。
87. LinkedList的内部结构是什么?
ArrayList包含内部数组中的元素,而LinkedList将它们存储在双向链表中。这意味着每个元素都包含到前一个元素和下一个元素的链接。第一个元素不链接到前一个元素(毕竟,它是第一个)。它也被认为是列表的头部,并且LinkedList对象可以直接引用它。同样,最后一个元素没有下一个元素,因为它是列表的尾部。LinkedList对象也直接引用它。这意味着访问列表的头或尾的时间复杂度是 O(1)。 在ArrayList中,如果列表增长,则内部数组也会增长。在这里一切都变得更容易:添加引用就像更改几个链接一样简单。让我们看看一些最常用的LinkedList方法: 1. add(<Element>) — 将一个元素添加到列表末尾,即在最后一个元素 (5) 之后,将添加指向新元素的链接作为下一个元素。新元素中的前一个引用将指向列表中现在位于其之前的元素 (5)。此操作的时间复杂度为 O(1),因为我们只需要链接到最后一个元素,并且您会记得,LinkedList 对象具有对尾部的直接引用,并且访问它具有最小的常量时间复杂度。2. add(<Index>, <Element>) — 按索引添加元素。假设在列表中间添加元素时,此方法首先从头部和尾部(两个方向)迭代元素,直到找到所需的位置。如果我们在第三个和第四个元素之间添加一个元素(如上图所示),那么第三个元素的下一个链接将指向新元素。新添加的元素的前一个将指向第三个。反过来,旧的第四个元素的前一个链接现在将指向新元素,新元素的下一个链接将指向新的第四个元素: 此方法的时间复杂度取决于新元素的索引:

- 如果它靠近头部或尾部,则操作将接近 O(1),因为实际上不需要迭代元素;
- 如果靠近中间,那么我们的时间复杂度为 O(N/2),因为该方法将同时从头部和尾部搜索,直到找到所需的元素。

88. HashMap的内部结构是什么?
这可能是 Java 开发人员候选人最常见的面试问题之一。HashMap使用键值对。它们如何存储在HashMap本身中?HashMap有一个内部节点数组:Node<K,V>[] table
默认情况下,数组的大小为 16,并且每次填充元素时都会加倍(即,当达到LOAD_FACTOR时;它指定数组可以达到的满阈值 - 默认情况下为0.75) 。每个节点都存储键的哈希值、键、值以及对下一个元素的引用: 
- 该单元格是空的——在这种情况下,新的节点值存储在其中。
- 单元格不为空——在这种情况下,将比较键的值。如果相等,则新的Node值覆盖旧的 Node 值;如果不相等,则访问下一个,并比较它的键...依此类推,直到新值覆盖某些旧值,或者到达单链表的末尾,然后将新值存储在那里作为最后一个元素。

GO TO FULL VERSION