JAVA集合面试题52道
时间:2022-09-21 00:00:00 元器件资讯
集容器概述
1.
什么是集合
集合是一个放置数据的容器准确地说是放数据对象引用的容器
集合存储是对象的引用,而不是对象本身
主要有集合类型
3
种:
set(
集)、
list(
列表)和
map(
映射
)
。
2.
集合的特点
集合的特点主要有以下两点:
集合用于存储对象的容器用于包装数据,对象较多也需要存储集中管理。
与数组对比对象的大小不确定。因为集合是可变长度。数组需要提前定义大小
3.
集合和数组的区别
固定长度的数组;集合可变长度。
数组可以存储基本数据类型或引用数据类型;集合只能存储引用数据类型。
数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
4.
使用集合框架的好处
1.
容量自增长;
2.
提供高性能的数据结构和算法,使编码更容易,提高程序速度和质量;
3.
可以方便地扩展或重写集合,提高代码的再利用性和可操作性。
4.
通过使用
JDK
自带集合类可以减少代码维护和学习
API
成本。
5.
常用的集合类有哪些?
Map
接口和
Collection
接口是所有集合框架的父接口:
1. Collection
接口的子接口包括:
Set
接口和
List
接口
2. Map
接口实现类主要包括:
HashMap
、
TreeMap
、
Hashtable
、
ConcurrentHashMap
以及
Properties
等
3. Set
接口实现类主要包括:
HashSet
、
TreeSet
、
LinkedHashSet
等
4. List
接口实现类主要包括:
ArrayList
、
LinkedList
、
Stack
以及
Vector
等
6. List
,
Set span style="color:#333333;">
,
Map
三者的区别?
Java
容器分为
Collection
和
Map
两大类,
Collection
集合的子接口有
Set
、
List
、
Queue
三种子接
口。我们比较常用的是
Set
、
List
,
Map
接口不是
collection
的子接口。
Collection
集合主要有
List
和
Set
两大接口
List
:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多
个
null
元素,元素都有索引。常用的实现类有
ArrayList
、
LinkedList
和
Vector
。
Set
:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一
个
null
元素,必须保证元素唯一性。
Set
接口常用实现类是
HashSet
、
LinkedHashSet
以及
TreeSet
。
Map
是一个键值对集合,存储键、值和之间的映射。
Key
无序,唯一;
value
不要求有序,允许重
复。
Map
没有继承于
Collection
接口,从
Map
集合中检索元素时,只要给出键对象,就会返回对应
的值对象。
Map
的常用实现类:
HashMap
、
TreeMap
、
HashTable
、
LinkedHashMap
、
ConcurrentHashMap
7.
集合框架底层数据结构
Collection
1. List
Arraylist
:
Object
数组
Vector
:
Object
数组
LinkedList
: 双向循环链表
2. Set
HashSet
(无序,唯一):基于
HashMap
实现的,底层采用
HashMap
来保存元素
LinkedHashSet
:
LinkedHashSet
继承与
HashSet
,并且其内部是通过
LinkedHashMap
来
实现的。有点类似于我们之前说的
LinkedHashMap
其内部是基于
Hashmap
实现一样,不
过还是有一点点区别的。
TreeSet
(有序,唯一): 红黑树
(
自平衡的排序二叉树。
)
Map
HashMap
:
JDK1.8
之前
HashMap
由数组
+
链表组成的,数组是
HashMap
的主体,链表则是
主要为了解决哈希冲突而存在的(
“
拉链法
”
解决冲突)
.JDK1.8
以后在解决哈希冲突时有了较
大的变化,当链表长度大于阈值(默认为
8
)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap
:
LinkedHashMap
继承自
HashMap
,所以它的底层仍然是基于拉链式散
列结构即由数组和链表或红黑树组成。另外,
LinkedHashMap
在上面结构的基础上,增加
了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的
操作,实现了访问顺序相关逻辑。
HashTable
: 数组
+
链表组成的,数组是
HashMap
的主体,链表则是主要为了解决哈希冲突
而存在的
TreeMap
: 红黑树(自平衡的排序二叉树)
8.
哪些集合类是线程安全的?
Vector
:就比
Arraylist
多了个
synchronized
(线程安全),因为效率较低,现在已经不太建议使
用。
hashTable
:就比
hashMap
多了个
synchronized (
线程安全
)
,不建议使用。
ConcurrentHashMap
:是
Java5
中支持高并发、高吞吐量的线程安全
HashMap
实现。它由
角色,
HashEntry
则用于存储键
-
值对数据。一个
ConcurrentHashMap
里包含一个
Segment
数组,
Segment
的结构和
HashMap
类似,是一种数组和链表结构;一个
Segment
里包含一个
HashEntry
数组,每个
HashEntry
是一个链表结构的元素;每个
Segment
守护着一个
HashEntry
数组里的元
素,当对
HashEntry
数组的数据进行修改时,必须首先获得它对应的
Segment
锁。(推荐使用)
...
9. Java
集合的快速失败机制
“fail-fast”
?
是
java
集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生
fail-fast
机制。
例如:假设存在两个线程(线程
1
、线程
2
),线程
1
通过
Iterator
在遍历集合
A
中的元素,在某个时
候线程
2
修改了集合
A
的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这
个时候程序就会抛出
ConcurrentModifificationException
异常,从而产生
fail-fast
机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个
modCount
变量。集
合在被遍历期间如果内容发生变化,就会改变
modCount
的值。每当迭代器使用
hashNext()/next()
遍历下一个元素之前,都会检测
modCount
变量是否为
expectedmodCount
值,是的话就返回遍
历;否则抛出异常,终止遍历。
解决办法:
1.
在遍历过程中,所有涉及到改变
modCount
值得地方全部加上
synchronized
。
2.
使用
CopyOnWriteArrayList
来替换
ArrayList
10.
怎么确保一个集合不能被修改?
可以使用
Collections. unmodififiableCollection(Collection c)
方法来创建一个只读集合,这样改变
集合的任何操作都会抛出
Java. lang. UnsupportedOperationException
异常。
示例代码如下:
Collection
接口
List
接口
11.
迭代器
Iterator
是什么?
Iterator
接口提供遍历任何
Collection
的接口。我们可以从一个
Collection
中使用迭代器方法来
获取迭代器实例。迭代器取代了
Java
集合框架中的
Enumeration
,迭代器允许调用者在迭代过程
中移除元素。
因为所有
Collection
接继承了
Iterator
迭代器
12. Iterator
怎么使用?有什么特点?
Iterator
使用代码如下:
List list = new ArrayList<>();
list. add("x");
Collection clist = Collections. unmodifiableCollection(list);
clist. add("y"); //
运行时此行报错
System. out. println(list. size());
List list = new ArrayList<>();
Iterator it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator
的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改
的时候,就会抛出
ConcurrentModifificationException
异常。
13.
如何边遍历边移除
Collection
中的元素?
边遍历边修改
Collection
的唯一正确方式是使用
Iterator.remove()
方法,如下:
一种最常见的
错误
代码如下:
运行以上错误代码会报
ConcurrentModifificationException
异常
。这是因为当使用
foreach(for(Integer i : list))
语句时,会自动生成一个
iterator
来遍历该
list
,但同时该
list
正在被
Iterator.remove()
修改。
Java
一般不允许一个线程在遍历
Collection
时另一个线程修改它。
14. Iterator
和
ListIterator
有什么区别?
Iterator
可以遍历
Set
和
List
集合,而
ListIterator
只能遍历
List
。
Iterator
只能单向遍历,而
ListIterator
可以双向遍历(向前
/
后遍历)。
ListIterator
实现
Iterator
接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元
素、获取前面或后面元素的索引位置。
15.
遍历一个
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
。
Iterator it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
for(Integer i : list){
list.remove(i)
}
推荐的做法就是,支持
Random Access
的列表可用
for
循环遍历,否则建议用
Iterator
或
foreach
遍历。
16.
说一下
ArrayList
的优缺点
ArrayList
的优点如下:
ArrayList
底层以数组实现,是一种随机访问模式。
ArrayList
实现了
RandomAccess
接口,
因此查找的时候非常快。
ArrayList
在顺序添加一个元素的时候非常方便。
ArrayList
的缺点如下:
删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性
能。
插入元素的时候,也需要做一次元素复制操作,缺点同上。
ArrayList
比较适合顺序添加、随机访问的场景。
17.
如何实现数组和
List
之间的转换?
数组转
List
:使用
Arrays. asList(array)
进行转换。
List
转数组:使用
List
自带的
toArray()
方法。
代码示例:
18. ArrayList
和
LinkedList
的区别是什么?
数据结构实现:
ArrayList
是动态数组的数据结构实现,而
LinkedList
是双向链表的数据结构实
现。
随机访问效率:
ArrayList
比
LinkedList
在随机访问的时候效率要高,因为
LinkedList
是线性的数
据存储方式,所以需要移动指针从前往后依次查找。
增加和删除效率:在非首尾的增加和删除操作,
LinkedList
要比
ArrayList
效率要高,因为
ArrayList
增删操作要影响数组内的其他数据的下标。
内存空间占用:
LinkedList
比
ArrayList
更占内存,因为
LinkedList
的节点除了存储数据,还存储
了两个引用,一个指向前一个元素,一个指向后一个元素。
线程安全:
ArrayList
和
LinkedList
都是不同步的,也就是不保证线程安全;
综合来说,在需要频繁读取集合中的元素时,更推荐使用
ArrayList
,而在插入和删除操作较多
时,更推荐使用
LinkedList
。
LinkedList
的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向
直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结
点和后继结点。
// list to array
List list = new ArrayList();
list.add("123");
list.add("456");
list.toArray();
// array to list
String[] array = new String[]{"123","456"};
Arrays.asList(array);
19. ArrayList
和
Vector
的区别是什么?
这两个类都实现了
List
接口(
List
接口继承了
Collection
接口),他们都是有序集合
线程安全:
Vector
使用了
Synchronized
来实现线程同步,是线程安全的,而
ArrayList
是非
线程安全的。
性能:
ArrayList
在性能方面要优于
Vector
。
扩容:
ArrayList
和
Vector
都会根据实际的需要动态的调整容量,只不过在
Vector
扩容每次
会增加
1
倍,而
ArrayList
只会增加
50%
。
Vector
类的所有方法都是同步的。可以由两个线程安全地访问一个
Vector
对象、但是一个线程访问
Vector
的话代码要在同步操作上耗费大量的时间。
Arraylist
不是同步的,所以在不需要保证线程安全时时建议使用
Arraylist
。
20.
插入数据时,
ArrayList
、
LinkedList
、
Vector
谁速度较快?阐述
ArrayList
、
Vector
、
LinkedList
的存储性能和特性?
ArrayList
和
Vector
底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便
增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操
作,所以索引数据快而插入数据慢。
Vector
中的方法由于加了
synchronized
修饰,因此
Vector
是线程安全容器,但性能上较
ArrayList
差
。
LinkedList
使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需
要记录当前项的前后项即可,所以
LinkedList
插入速度较快
。
21.
多线程场景下如何使用
ArrayList
?
ArrayList
不是线程安全的,如果遇到多线程场景,可以通过
Collections
的
synchronizedList
方
法将其转换成线程安全的容器后再使用。例如像下面这样:
22.
为什么
ArrayList
的
elementData
加上
transient
修饰?
ArrayList
中的数组定义如下:
private transient Object[] elementData;
再看一下
ArrayList
的定义:
List synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
可以看到
ArrayList
实现了
Serializable
接口,这意味着
ArrayList
支持序列化。
transient
的作用
是说不希望
elementData
数组被序列化,重写了
writeObject
实现:
每次序列化时,先调用
defaultWriteObject()
方法序列化
ArrayList
中的非
transient
元素,然后
遍历
elementData
,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后
的文件大小。
23. 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
接口
24.
说一下
HashSet
的实现原理?
HashSet
是基于
HashMap
实现的,
HashSet
的值存放于
HashMap
的
key
上,
HashMap
的
value
统
一为
present
,因此
HashSet
的实现比较简单,相关
HashSet
的操作,基本上都是直接调用底层
HashMap
的相关方法来完成,
HashSet
不允许重复的值。
25. HashSet
如何检查重复?
HashSet
是如何保证数据不可重复的?
向
HashSet
中
add ()
元素时,判断元素是否存在的依据,不仅要比较
hash
值,同时还要结合
equles
方法比较。
HashSet
中的
add ()
方法会使用
HashMap
的
put()
方法。
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
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
HashMap
HashSet
实现了
Map
接口
实现
Set
接口
存储键值对
仅存储对象
调用
put
()向
map
中
添加元素
调用
add
()方法向
Set
中添加元素
HashMap
使用键
(
Key
)计算
Hashcode
HashSet
使用成员对象来计算
hashcode
值,对于两个对象来说
hashcode
可能相同,所以
equals()
方法用来判断