集合概览
可能会有面试官想先试探一下问:说一说你对Java集合的了解 或者你都了解哪些东西。
这时候要先概述一下:Java常用的集合主要就两大类,一类是Collection。。。一类是Map接口下的集合。。。
List、Set、Queue、Map四个接口的区别
-
List:元素有序、可重复;
-
Set:元素不可重复,一般情况下,比如HashSet元素存储无序,经过改造的LinkedHashSet和TreeSet元素存储有序
-
Queue:按特定的排序规则来确定顺序。元素有序、可重复
-
Map:键值对存储,key不可重复、无序。value可重复、无序。一般情况下,比如HashMap元素存储无序。经过改造 的比如:LinkedHashMap和TreeMap元素存储有序?
集合框架底层数据结构
需要再详细一点
Collection接口下的集合
List
-
ArrayList:Object[ ]数组
-
Vector:Object[ ]数组
-
LinkedList:双向链表
-
Stack:
Set
-
HashSet:无序、唯一。底层使用HashMap来存储元素
-
LinkedHashSet:HashSet的子类,内部通过LinkedHashMap实现,有序的
-
TreeSet:有序,实现了SortedSet接口,内部通过红黑树来实现
Queue
-
PriorityQueue:Object[ ] 数组来实现小顶堆
-
DelayQueue:PriorityQueue来实现的
-
ArrayDeque: 可扩容动态双向数组。
Map接口下的集合
-
HashMap:jdk1.8之前HashMap由数组+链表组成,数组是HashMap的主体,链表主要是为了解决哈希冲突存在的。JDK1.8以后在解决哈希冲突时有了变化,当链表长度大于阈值(默认为8)(将链表转为红黑树前会判断,如果当前数据的长度小于64, 则会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
-
LinkedHashMap:继承自HashMap,所以底层跟HashMap一样。但在此基础上,增加了双向链表,使得可以保持插入时的键值对顺序。同时对链表进行相应操作,实现了?
-
Hashtable:数组+链表组成。同HashMap
-
TreeMap:红黑树
List
主要实现类的继承关系图
ArrayList的继承关系图
ArrayList的常用方法
插入
ArrayList提供的插入元素的方法
add():尾部追加,方法返回值boolenadd(int index, E element):指定位置插入,方法返回值voidaddAll(集合):尾部追加一个集合,方法返回值booleanaddAll(int index, 集合),指定位置插入一个集合,方法返回值void
删除
ArrayList提供的删除元素的方法
remove(int index):删除指定位置的元素,方法返回这个被删除的元素remove(Object o):删除某个元素,一般是删除列表中第一次出现的这个元素,方法返回booleanremoveAll(Collection<?> c):删除指定集合里面的所有元素,方法返回booleanremoveIf(Predicate<? super E> filter):删除满足一定条件的元素,方法返回booleanremoveRange(int fromIndex, int toIndex):删除指定范围内的元素,左闭右开。clear:清空数组中的所有元素。
初始化数组到ArrayList
ArrayList<String> animals = new ArrayList<>(Arrays.asList("Cat", "Cow", "Dog"));
Arrays.asList的返回值是List类型
其实是用了构造方法:ArrayList(Collection<? extends E> c)
修改
set(int index, E element):将指定位置的元素修改为传入的数值
查询
-
get(int index):获取指定位置的元素,方法返回该元素 -
使用
iterator()方法class Main { public static void main(String[] args){ ArrayList<String> animals = new ArrayList<>(); //在数组列表中添加元素 animals.add("Dog"); animals.add("Cat"); animals.add("Horse"); animals.add("Zebra"); //创建一个Iterator对象 Iterator<String> iterate = animals.iterator(); System.out.print("ArrayList: "); //使用Iterator的方法访问元素 while(iterate.hasNext()){ System.out.print(iterate.next()); System.out.print(", "); } } }
LinkedList的继承关系图
LinkedList的常用方法
- 如果引用类型是LinkedList,可以用的方法是以下两种情况的综合。
- 如果引用类型是List,可以用的方法就是和ArrayList的一样
- 如果引用类型的Deque,可以用队列的一些方法,如下
- 添加
- addFirst
- addLast
- offerFirst
- offerLast和offer
- push,栈头加入,
- 查询
- get
- getFirst
- getLast
- peekFirst
- peekLast
- 查询并删除
- pollFirst
- pollLast
- poll
- 删除
- removeFirst
- removeLast
Vector的继承关系图
Stack类的继承关系图
Stack类的常用方法
除了从Vector类继承来的方法,还有以下方法可用:
push():元素入栈peek():查询栈顶元素pop():弹出栈顶元素(删除元素并返回该元素)
ArrayList和数组(Array)的区别
-
ArrayList内部也是基于数组实现的,经过一系列的操作,可以实现动态扩容。但是数组创建好之后就不能修改长度了。
-
ArrayList中只能存储对象。对于基本类型,可以使用对应的包装类。数组可以存储对象,也可以存储基本类型。
-
ArrayList提供了很多方法,支持追加元素、插入元素、删除等常见操作。Array是一个固定的数组,只能通过下标访问元素。
-
ArrayList创建时不需要指定大小,代码里默认的初始容量大小是10。Array创建时必须指定数组的大小。
ArrayList和Vector的区别
-
Vector对元素进行操作的各种方法,都加了synchronized关键字,做了线程同步;ArrayList不是线程安全的
-
Vector对元素进行扩容的时候,是增加一倍;ArrayList扩容是增加0.5倍? 目前Vector类已经很少使用了,每个方法都做线程同步,性能比较差。一般业务代码中要做线程安全,都是一系列操作加锁,所以没必要使用Vector。而且现在也有更新的线程安全集合CopyOnWriteArrayList可以使用。
CopyOnWriteArrayList是怎么实现线程安全的
ArrayList插入和删除元素的时间复杂度是多少
插入
- 头部插入:O(n)
- 尾部追加:如果容量未超过限制,则O(1);否则要扩容,做一次复制,时间复杂度是O(n)
- 中间位置插入:O(n)
删除
- 头部删除:需要将所有元素向前移动一个位置,O(n)
- 尾部删除:O(1)
- 指定位置删除:删除之后,需要移动元素,O(n)
LinkedList插入和删除元素的时间复杂度
- 头部插入/删除:只需要修改头结点的指针即可。O(1)
- 尾部追加/删除:只需要修改尾部结点的指针即可,O(1)
- 指定位置插入/删除:需要先遍历链表,定位到指定位置,再修改结点的指针,复杂度O(n)
LinkedList 为什么不能实现 RandomAccess 接口?
RandomAccess 是一个标记接口,用来表明实现该接口的类支持随机访问(即可以通过索引快速访问元素)。由于 LinkedList 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess 接口。
ArrayList与LinkedList的区别
底层数据结构不同。ArrayList底层是通过Object类型的数组实现的;LinkedList底层是使用的双向链表结构 所以有以下区别
-
插入和删除元素的时间复杂度不同。见上述两个问题
-
访问元素的时间复杂度不同。ArrayList可以通过下标,快速获取到元素。但是LinkedList需要遍历所有的结点并找到指定的元素
-
内存空间占用。ArrayList占用的内存空间通常就是所有元素的大小+一些预留的空间;LinkedList,每个元素都需要多占用一部分空间,要存储前一个结点和后一个结点的指针。
说一说ArrayList的扩容机制
详见文章:ArrayList扩容机制分析
判断是否扩容的流程图
总结:
- 若调用无参的构造方法进行初始化,则在添加第1个元素的时候需要进行一次扩容
- 添加第11个元素的时候,会进行一次扩容
- 再往后同理,比较元素数量和当前数组大小,判断是否进行扩容
扩容的步骤
总结:
Queue
主要实现类的继承关系图
LinkedList的继承关系图
ArrayDeque的继承关系图
PriorityQueue的继承关系图
BlockingQueue(接口,并发)
Queue与Deque的区别
-
结构上的区别:Queue是单端队列,只能从一端插入元素,另一端删除元素,遵循先进先出的原则;Deque是双端队列,两端都可以插入和删除元素
-
使用语法上的区别:如下 Queue操作元素的时候,有两种返回值,一种是抛出异常,另一种是返回特殊值
Queue 接口 | 抛出异常 | 返回特殊值 |
|---|---|---|
| 插入队尾,返回boolean | add(E e) | offer(E e) |
| 删除队首,成功则返回删除的元素 | remove() | poll() |
| 查询队首元素 | element() | peek(),无值则返回null |
Deque 扩展了 Queue 的接口, 增加了在队首和队尾进行插入和删除的方法(Queue中的方法也有),同样根据失败后处理方式的不同分为两类:
Deque 接口 | 抛出异常 | 返回特殊值 |
|---|---|---|
| 插入队首 | addFirst(E e) | offerFirst(E e) |
| 插入队尾 | addLast(E e) | offerLast(E e) |
| 删除队首 | removeFirst() | pollFirst() |
| 删除队尾 | removeLast() | pollLast() |
| 查询队首元素 | getFirst() | peekFirst() |
| 查询队尾元素 | getLast() | peekLast() |
事实上,Deque 还提供有 push() 和 pop() 等其他方法,可用于模拟栈。
ArrayDeque与LinkedList的区别
- ArrayDeque是基于数组和双指针实现的,LinkedList是通过链表实现的
- ArrayDeque不能存储NULL,LinkedList是支持的
- ArrayDeque插入元素的时候可能会存在扩容操作,LinkedList不存在这种问题
PriorityQueue
特点:
-
元素出队顺序,与优先级相关。优先级最高的元素先出队
-
利用二叉堆的数据结构来实现,底层使用可变长的数组来存储数据
-
通过堆元素的上浮和下沉,实现了O(logn)时间复杂度内插入和删除堆顶元素
-
非线程安全,且不支持存储NULL和non-comparable的对象
-
默认是小顶堆,但可以接收Comparator作为构造参数,来自定义元素优先级的先后。
什么是 BlockingQueue?
ArrayBlockingQueue 和 LinkedBlockingQueue 有什么区别?
以上所有集合涉及的源码分析稍后再写
Set
主要实现类的继承关系图
HashSet类的继承关系图
TreeSet类的继承关系图
LinkedHashSet类的继承关系图
Comparable和Comparator接口的区别
Comparable 接口和 Comparator 接口都是 Java 中用于排序的接口,通常用于对象之间比较大小、排序
-
Comparable接口是java.lang包的一个接口,有一个compareTo(Object obj)方法用来排序 -
Comparator接口是在java.util包,用于排序的方法是compare(Object obj1, Object obj2)
一般需要对一个集合进行自定义排序的时候,需要重写上面两个方法。
无序性和不可重复性的含义是什么
-
无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
-
不可重复性是指添加的元素按照
equals()判断时 ,两个元素不能是相同的。
HashSet、LinkedHashSet、TreeSet的异同
相同点
- 都实现了Set接口,能保证元素唯一,都不是线程安全的
不同点 底层数据结构不同,所以很多特性也不一样
-
HashSet的底层是基于HashMap实现的,所以元素不重复;添加、删除、查找元素都是O(1)的时间复杂度;元素是无序的。
-
LinkedHashSet是HashSet的子类,有HashSet的性质;初始化时调用父类的构造方法:
HashSet(int initialCapacity, float loadFactor, boolean dummy),这个方法基于LinkedHashMap实现,所以又有链表的性质,也就是可以保持元素插入的顺序。 -
TreeSet实现了SortSet的接口,底层是红黑树,所以是有序的;元素也是不重复的;
Map
单独放到一篇文章里说:Java集合:Map面试题
被问过的面试题
-
java的hashset、sortset的区别
-
java的hashset、linkedhashset的区别
-
java有哪些表示数组的集合
-
数组和链表的区别
-
堆和队列的区别
-
常用的哪些类
答:String、ArrayList和LinkedList
-
ArrayList和LinkedList的区别
-
堆和栈的区别