Java面试题--Java集合容器

一、集合容器概述

1.集合和数组的区别

  • 数组是固定长度的,集合是可变长度的

  • 数组可以用于存储基本数据类型,也可以存储引用数据类型(比如String)。集合只能存储引用数据类型

  • 数组存储的元素必须是同一数据类型,集合存储的对象可以是不同数据类型

2.常用集合框架有哪些

Collection接口和Map接口是所有集合框架的父接口,Collection存储着对象的集合,Map存储着键值对(两个对象)的映射表

  • Collection的子接口包括:Set,List,Queue

  • Map接口的实现类包括:HashMap,HashTable,LinkedHashMap,TreeMap

  • Set接口的实现类包括:HashSet,TreeSet,LinkedHashSet

  • List接口的实现类包括:ArrayList,LinkedList,Vector,Stack

3.List,Set,Map三者的区别?List,Set,Map是否都继承自Collection接口?List,Set,Map三个接口存取元素时各有什么特点?

Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。Map接口不是collection的子接口。

Collection接口主要分为List和Set两大类:

  • List:一个有序容器(元素存入集合的顺序和取出的一致),元素可以重复,可以加入多个null元素,元素都有索引,常见的实现类是:ArrayList,LinkedList,Vector,Stack

  • Set:一个无序容器(元素存入集合的顺序和取出的不一定一致),元素不可以重复,只能加入一个null元素,必须保证元素唯一性,常见的实现类是:HashSet,TreeSet,LinkedHashSet

Map接口存储着键值对(两个对象)的映射表。Key无序,唯一,value不要求有序,也不要求唯一。常见的实现类是:HashMap,HashTable,TreeMap,LinkedHashMap,ConcurrentHashMap

4.集合框架底层数据结构

1.Collection

(1)List

  • ArrayList:基于动态数组实现,支持随机访问

  • Vector:和 ArrayList 类似,但它是线程安全的

  • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列

(2)Set

  • HashSet(无序,唯一):基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的

  • LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序

  • TreeSet(有序,唯一):基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)

(3)Queue

  • LinkedList:可以用它来实现双向队列

  • PriorityQueue:基于堆结构实现,可以用它来实现优先队列

2.Map(下图的父接口应该是Map接口

  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间

  • LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。

  • HashTable:和HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁

  • TreeMap: 红黑树(自平衡的排序二叉树)

5.哪些集合类是线程安全的

  • vector:就比ArrayList多了个同步机制,因为效率低,现在已经不推荐使用这个

  • stack:先进后出,继承自Vector

  • HashTable:比HashMap多个线程安全,但是不应该使用它,应该使用效率更高的ConcurrentHashMap

6.Java集合的快速失败机制:fail-fast

是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

  • 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized
  • 使用CopyOnWriteArrayList来替换ArrayList

7.如何确保一个集合类不被修改

可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());


二、容器中的设计模式

1.迭代器模式

Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。

从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String item : list) {
    System.out.println(item);
}

2.适配器模式

java.util.Arrays#asList() 可以把数组类型转换为 List 类型。


三、Collection接口相关面试题

1.迭代器 Iterator 是什么?

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

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

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

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

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遍历

  • for each: foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换

  • iterator

6.Collection和Collections有什么区别(易混淆)

  • Collection 是一个集合接口,它提供了对对象进行基本操作的通用接口方法

  • Collections 是一个包装类,包含很多静态方法不能被实例化,就像一个工具类,比如Collections.sort(list)


四、ArrayList相关面试题

1. ArrayList 的优缺点

ArrayList的优点如下:

  • ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快

  • ArrayList 在顺序添加一个元素的时候非常方便

ArrayList 的缺点如下:

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

  • 插入元素的时候,也需要做一次元素复制操作,缺点同上

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

2.ArrayList 和 LinkedList 的区别是什么?

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。

  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。

  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

  • 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

  • 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

3.ArrayList 和 Vector 的区别是什么?

  • 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。

  • 性能:ArrayList 在性能方面要优于 Vector。

  • 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

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


参考链接

https://thinkwon.blog.csdn.net/article/details/104588551

https://github.com/CyC2018/CS-Notes

原文地址:https://www.cnblogs.com/swifthao/p/12852338.html