Java集合类面试题: Collection 接口(List、Set)

收集大量Java经典面试题目📚,内容涵盖了包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 等知识点🏝️。适合准备Java面试的读者参考和复习🌟📢。

❗ ❗ ❗ 关注公众号:枫蜜柚子茶 ✅✅🗳
📑 回 复 “ Java面试 ” 获 取 完 整 资 料⬇ ⬇ ⬇

📖Java集合类面试题目Top52道题🔥🔥
1️⃣ Java 集 合 容 器 概 述 
2️⃣ Collection 接 口 🚩
3️⃣ Map 接 口 
4️⃣ 辅 助 类 工 具

一、List接口

1. 迭代器 Iterator 是什么?

  •         ◾  Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
  •         ◾  因为所有Collection接继承了Iterator迭代器。

2. Iterator 怎么使用❓ 有什么特点❓

        ◾  Iterator 使用代码如下:

List<String> list = new ArrayList<>();  Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}

        ◾ Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改 的时候,就会抛出 ConcurrentModicationException 异常。

3. 如何边遍历边移除 Collection 中的元素?

        ◾  边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下

Iterator<Integer> it = list.iterator();
while(it.hasNext()){ *// do something* it.remove();
}

一种最常见的错误代码如下:

for(Integer i : list){ list.remove(i)

}

📛 运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。

4. Iterator ListIterator 有什么区别?

  •  ◾ Iterator 可以遍历 Set List 集合,而 ListIterator 只能遍历 List
  •  ◾ Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
  •  ◾ ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元 素、获取前面或后面元素的索引位置。

5. 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么? Java List 遍历的最佳实践是什么?

🔻 遍历方式有以下几种:

  •         1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素, 当读取到最后一个元素后停止。
    •         2. 迭代器遍历, Iterator Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。 Java Collections 中支持了 Iterator 模式。
      •         3. foreach 循环遍历。 foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过 程中操作数据集合,例如删除、替换。

最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支 Random Access

  •  ◾  如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均 时间复杂度为 O(1),如ArrayList
  •  ◾ 如果没有实现该接口,表示不支持 Random Access,如LinkedList

 ◾ 推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator  foreach 遍历。

6. 说一下 ArrayList 的优缺点

ArrayList的优点如下:

  •        ◾  ArrayList 底层以数组实现,是一种随机访问模式。 ArrayList 实现了 RandomAccess 接口, 因此查找的时候非常快。
    •        ◾  ArrayList 在顺序添加一个元素的时候非常方便。

ArrayList 的缺点如下:

  •        ◾  删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性 能。
  •        ◾ 插入元素的时候,也需要做一次元素复制操作,缺点同上。

📋 ArrayList 比较适合顺序添加、随机访问的场景。

7. 如何实现数组和 List 之间的转换?

  •   ◾  数组转 List:使用 Arrays. asList(array) 进行转换。
  •   ◾  List 转数组:使用 List 自带的 toArray() 方法。 

​​​​​ 📐代码示例:

// list to array
List<String> list = new ArrayList<String>(); list.add("123");
list.add("456"); list.toArray();

// array to list
String[] array = new String[]{"123","456"}; Arrays.asList(array);

8. ArrayList LinkedList 的区别是什么?

  • ◾  数据结构实现: ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
  •  随机访问效率: ArrayList LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  •  增加和删除效率:在非首尾的增加和删除操作,  LinkedList ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
  • ◾  内存空间占用: LinkedList ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用, 一个指向前一个元素, 一个指向后一个元素。
  • ◾  线程安全: ArrayList LinkedList 都是不同步的,也就是不保证线程安全;
  •   综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList
  • ◾   LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向 直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结 点和后继结点。

9. ArrayList Vector 的区别是什么?

🔻 这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合:

  •         ◾   线程安全: Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非 线程安全的。
    •         ◾   性能:ArrayList 在性能方面要优于 Vector
      •         ◾  扩容:ArrayList Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次 会增加 1 倍,而 ArrayList 只会增加 50%

🔻 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问 Vector的话代码要在同步操作上耗费大量的时间。

        ◾   Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist

10. 插入数据时, ArrayList LinkedListVector谁速度较快?阐述 ArrayListVector LinkedList 的存储性能和特性?

  •  ◾  ArrayListVector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便 增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操  作,所以索引数据快而插入数据慢。
  •  ◾  Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较 ArrayList
  •  ◾  LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需 要记录当前项的前后项即可,所以 LinkedList 插入速度较快

11. 多线程场景下如何使用ArrayList

 ⭕  ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections synchronizedList  法将其转换成线程安全的容器后再使用。例如像下面这样:

List<String> synchronizedList = Collections.synchronizedList(list); synchronizedList.add("aaa");
synchronizedList.add("bbb");

for (int i = 0; i < synchronizedList.size(); i++) { System.out.println(synchronizedList.get(i));
}

12. 为什么 ArrayList elementData 加上 transient 修饰?

   ArrayList 中的数组定义如下:

private transient Object[] elementData;

   再看一下 ArrayList 的定义:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

        ◾  可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。 transient 的作用 是说不希望 elementData 数组被序列化,重写了 writeObject 实现:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{

*// Write out element count, and any hidden stuff*

int expectedModCount = modCount; s.defaultWriteObject();

*// Write out array length*

s.writeInt(elementData.length);

*// Write out all elements in the proper order.* for (int i=0; i<size; i++)

s.writeObject(elementData[i]); if (modCount != expectedModCount) {

throw new ConcurrentModificationException(); }

        ◾   每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后 遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后 的文件大小。

13. List Set 的区别

📝   List , Set 都是继承自Collection 接口

  •         ◾  List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多 null元素,元素都有索引。常用的实现类有 ArrayList LinkedList Vector
    •         ◾   Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一 null元素,必须保证元素唯一性。 Set 接口常用实现类是 HashSet LinkedHashSet 以及TreeSet。
      •         ◾  另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无 序,无法用下标来取得想要的值。

🏅Set和List对比

  •         ◾  Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
    •         ◾  List:和数组类似, List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起 其他元素位置改变

二、Set接口

14. 说一下 HashSet 的实现原理?

        ◾  HashSet 是基于 HashMap 实现的, HashSet的值存放于HashMapkey上, HashMapvalue 一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层   HashMap 的相关方法来完成, HashSet 不允许重复的值。

15. HashSet如何检查重复? HashSet是如何保证数据不可重复的?

  •         ◾  向HashSet add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合 equles 方法比较。
  •         ◾  HashSet 中的add ()方法会使用HashMap put()方法。
    •         ◾   HashMap key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap key 并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧V,然后返回旧的V。所以不会重复(   HashMap 比较key是否相等是先比较hashcode 再比较equals )。

   以下是HashSet 部分源码:

private static final Object PRESENT = new Object(); private transient HashMap<E,Object> map;

public HashSet() {
map = new HashMap<>(); }

public boolean add(E e) {
// 调用HashMap的put方法 ,PRESENT是一个至始至终都相同的虚值 return map.put(e, PRESENT)==null;
}
  • 📢 hashCode()与equals ()的相关规定

1️⃣. 如果两个对象相等,则hashcode一定也是相同的。hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。

2️⃣. 两个对象相等,对两个equals方法返回true。

3️⃣. 两个对象有相同的hashcode值,它们也不一定是相等的。

4️⃣. 综上equals方法被覆盖过,则hashCode方法也必须被覆盖。

5️⃣. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个 对象无论如何都不会相等(即使这两个对象指向相同的数据)。

🔰 ==与equals的区别:

        ◾ ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存 空间的值是不是相同。

        ◾ ==是指对内存地址进行比较 equals()是对字符串的内容进行比较。

26. HashSetHashMap的区别

HashMap

HashSet

实现了Map接口

实现Set接口

存储键值对

仅存储对象

调用put()map 添加元素

调用add()方法向Set中添加元素

HashMap使用键 Key)计算

Hashcode

HashSet使用成员对象来计算hashcode值,对于两个对象来说   hashcode可能相同,所以equals()方法用来判断对象的相等性, 如果两个对象不同的话,那么返回false

HashMap相对于

HashSet较快,因为它 是使用唯一的键获取对

HashSetHashMap来说比较慢

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫蜜柚子茶

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值