【java面试】- 集合篇
Java 集合概览
-
从下图可以看出,在
Java
中除了以Map
结尾的类之外, 其他类都实现了Collection
接口。并且,以Map
结尾的类都实现了Map
接口 -
List、Set、Map三者的区别
List
:存储的元素是有序的、可重复的Set
:存储的元素是无序的、不可重复的Map
:使用键值对(kye-value)存储,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值
-
集合底层数据结构
List
Arraylist
: Object[]数组Vector
:Object[]数组LinkedList
:双向链表(JDK1.6
之前为循环链表,JDK1.7
取消了循环)
Set
HashSet
(无序,唯一): 基于HashMap
实现的,底层采用HashMap
来保存元素LinkedHashSet
:LinkedHashSet
是HashSet
的子类,并且其内部是通过LinkedHashMap
来实现的。有点类似于我们之前说的LinkedHashMap
其内部是基于 HashMap 实现一样,不过还是有一点点区别的TreeSet
(有序,唯一): 红黑树(自平衡的排序二叉树)
Map
HashMap
:JDK1.8
之前HashMap
由数组+链表组成的,数组是HashMap
的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8
以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8
)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间LinkedHashMap
:LinkedHashMap
继承自HashMap
,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap
在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑Hashtable
:数组+链表组成的,数组是HashMap
的主体,链表则是主要为了解决哈希冲突而存在的TreeMap
: 红黑树(自平衡的排序二叉树)
-
Iterator
: 迭代器,可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义hasNext()
和next()
方法,但这样做会让整个集合体系过于臃肿,于是就有了迭代器- 作用: 用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出
ConcurrentModificationException
异常 - 使用:
- 作用: 用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出
Map<Integer, String> map = new HashMap<>();
map.put(1, "Java");
map.put(2, "C++");
map.put(3, "PHP");
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey() + entry.getValue());
}
-
关于线程不安全的集合及解决方案
Arraylist
,LinkedList
,Hashmap
,HashSet
,TreeSet
,TreeMap
,PriorityQueue
都不是线程安全的- 解决方案:使用线程安全的集合
ConcurrentHashMap
: 可以看作是线程安全的HashMap
CopyOnWriteArrayList
: 可以看作是线程安全的ArrayList
,在读多写少的场合性能非常好,远远好于Vector
ConcurrentLinkedQueue
: 高效的并发队列,使用链表实现。可以看做一个线程安全的LinkedList
,这是一个非阻塞队列BlockingQueue
: 这是一个接口,JDK
内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道ConcurrentSkipListMap
: 跳表的实现。这是一个Map
,使用跳表的数据结构进行快速查找
-
Arraylist
和Vector
的区别ArrayList
是List
的主要实现类,底层使用Object[]
存储,适用于频繁的查找工作,线程不安全Vector
是List
的古老实现类,底层使用Object[]
存储,线程安全的。
-
Arraylist
和LinkedList
的区别ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全Arraylist
底层使用的是Object
数组;LinkedList
底层使用的是双向链表
数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环)ArrayList
采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响,LinkedList
采用链表存储,所以对于add(E e)
方法的插入,删除元素时间复杂度不受元素位置的影响,近似O(1)
,如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入LinkedList
不支持高效的随机元素访问,而ArrayList
支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)ArrayList
的空间浪费主要体现在list
列表的结尾会预留一定的容量空间,而LinkedList
的空间花费则体现在它的每一个元素都需要消耗比ArrayList
更多的空间(因为要存放直接后继和直接前驱以及数据)
扩展:双向链表和双向循环链表
-
comparable 和 Comparator 的区别
comparable
接口实际上是出自java.lang
包 它有一个compareTo(Object obj)
方法用来排序comparator
接口实际上是出自java.util
包它有一个compare(Object obj1, Object obj2)
方法用来排序
-
无序性和不可重复性的含义
- 无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的
- 不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法
-
较
HashSet
、LinkedHashSet
和TreeSet
三者的异同HashSet
是Set
接口的主要实现类 ,HashSet
的底层是HashMap
,线程不安全的,可以存储null
值LinkedHashSet
是HashSet
的子类,能够按照添加的顺序遍历TreeSet
底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。
-
HashMap
和Hashtable
的区别HashMap
是非线程安全的,HashTable
是线程安全的,因为HashTable
内部的方法基本都经过synchronized
修饰- 因为线程安全的问题,
HashMap
要比HashTable
效率高一点 HashMap
可以存储null
的key
和value
,但null
作为键只能有一个,null
作为值可以有多个;HashTable
不允许有null
键和null
值,否则会抛出NullPointerException
- 初始容量大小跟每次扩充容量大小不同:
- 创建时如果不指定容量初始值,
Hashtable
默认的初始大小为11
,之后每次扩充,容量变为原来的2n+1
。HashMap
默认的初始化大小为16
。之后每次扩充,容量变为原来的2
倍 - 创建时如果给定了容量初始值,那么
Hashtable
会直接使用你给定的大小,而HashMap
会将其扩充为2
的幂次方大小。也就是说HashMap
总是使用2
的幂作为哈希表的大小 JDK1.8
以后的HashMap
在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8
)(将链表转换成红黑树前会判断,如果当前数组的长度小于64
,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable
没有这样的机制
- 创建时如果不指定容量初始值,
明明可以靠才华吃饭,非要靠脸~