前言
共13题。
说一说集合类的体系结构?
常见的集合主要有两大类,分别是单列集合和双列集合:
- 单列集合的顶级接口是
Collection,它下面有两个主要的子接口分别是List和Set。List的特点是元素有序的,可以重复的;Set的特点是元素无序的,不可重复的。List下我们常用的类有ArrayList、LinkedList等,Set下我们常用的类有HashSet、LinkedHashSet、TreeSet等。 - 双列集合的顶级接口是
Map,它的特点是键值对形式存在,且键不能重复。Map接口下我们常用的类有:HashMap、LinkedHashMap、TreeMap等。
聊聊集合类的底层数据结构?
集合主要分为单列集合和双列集合:
- 双列集合都是
Map的实现类,主要有HashMap、LinkedHashMap和TreeMap。HashMap: 数组+链表+红黑树;LinkedHashMap:继承自HashMap,在HashMap的基础上增加了一条双向链表;TreeMap:底层是红黑树。 - 单列集合主要是
List和Set。List有ArrayList和LinkedList,ArrayList的底层是数组,查询快,增删慢;LinkedList的底层是双向链表,查询慢,增删快;Set有HashSet、LinkedHashSet和TreeSet,它的实现原理和对应的Map是一样的,底层都是用的对应Map的key实现。
ArrayList和LinkedList的区别?
ArrayList和LinkedList都是单列集合,都是有序,可重复的。不同点有:
ArrayList的底层是动态数组,而LinkedList底层是双向链表。ArrayList查询快,增删慢,适合查询场景;LinkedList查询慢,增删快,适合频繁修改的场景。LinkedList比ArrayList更占内存,这是因为它的每个节点除了存储数据,还存储了前后节点的引用。
HashMap和HashTable的区别?
- 调用
put方法时,HashTable有锁保证线程安全,但效率较低;HashMap没有,但效率高。 HashMap较为常用,HashTable不太常用,且多线程场景一般会使用ConcurrentHashMap(采用了乐观锁和悲观锁)。
HashMap的底层原理?
HashMap的底层数据结构是哈希表,哈希表在JDK1.8之前是数组+链表实现,在JDK1.8之后是数组+链表+红黑树实现的。
下面以map中存储对象的流程来说一下它的实现原理:
-
当创建一个
HashMap时,JDK就会在内存中创建一个长度为16的数组; -
当调用
put方法向HashMap中保存一个元素时,它会先调用key的hashCode方法计算出key的hash值; -
然后用得到的
hash值对数组长度取余,找出当前对象的元素在数组中的位置; -
接着它会判断该位置上是否有元素,如果没有,就会将此元素直接存储到当前位置上;
-
如果算出的位置上有元素或者链表,它会再调用
key的equals方法跟存在元素的key做比较; -
如果有一个比较得到的结果为
true,则会进行值的覆盖,如果都为false,则会将元素追加在链表的末尾。
为了降低Hash冲突和链表长度,HashMap还做了一些优化:
- 当元素的数量超过数组大小与负载因子的乘积时,就会执行扩容,扩容为原来的
2倍,并将原来数组中的键重新进行hash运算,然后分配到新数组中; - 当链表的长度>
8,并且数组长度>=64时,链表会转换为红黑树,当红黑树结点数小于6时将再次转回为链表。
HashMap是怎么解决哈希冲突的?
解决hash冲突最常用的方式有链表法和开放地址法,HashMap采用了链表法。
当哈希冲突出现后,HashMap会在发生冲突的位置上创建一个链表来保存元素。在JDK1.8之后,又对此做出了改进,那就是当链表的长度>8,并且数组长度>=64的时候,链表就会转换为红黑树,使得效率更高。而当红黑树节点个数小于6时,红黑树又转换回到链表。
HashMap的扩容机制是怎样的?
当HashMap中的元素个数超过数组长度乘以负载因子时,会重新分配一个更大的数组,将原来的元素重新计算哈希值并插入到新的数组中。
在JDK1.8中,底层是调用resize方法实现扩容的。它的默认做法是:当元素个数超过数组长度的0.75倍时触发扩容,每次扩容的时候,都是扩容为原来的2倍, 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
为何HashMap的数组长度一定是2的次幂?
- 提高
HashMap存取数据的效率; - 尽可能地减少
hash值的碰撞; - 提高计算索引时和扩容时重新计算索引的效率。
说一下HashSet的实现原理?
HashSet是基于HashMap实现的。HashSet的值存放于HashMap的key上,因此HashSet的实现比较简单。HashSet的操作也基本上都是直接调用底层HashMap的相关方法来完成。由于HashMap的键是不能重复的,所以HashSet不允许重复的值。
HashSet如何检查重复?
HashSet底层不会产生链表
通过哈希表来实现,具体做法是:
-
在向
HashSet中保存元素的时候,会先计算该元素的哈希值,确定他在数组中的位置; -
如果该位置上没有元素,则保存成功,如果有元素,则调用
equals方法去跟存在的每个值进行比较; -
比较结果有一个相等,则直接丢弃;不相等,就存到
HashSet中。
如何删除正在循环遍历的集合方法?
不能删除正在循环遍历的集合,要先将要删除的存到大集合中,循环遍历后删除大集合,有以下几种方法:迭代器(iterator),stream流(removeIf)--底层是迭代器。
集合中都有哪些类是线程安全的?
集合中线程安全的类,我知道的有:
- 双列集合:
Hashtable、ConcurrentHashMap - 单列集合:
Vector、Stack
此外还可以通过Collections.synchronized* 工厂方法创建线程安全的集合,例如Collections.synchronizedList(new ArrayList<>())。
ArrayList和普通数组有什么区别?
- 类型限制:数组可以存储基本类型,而
ArrayList存储的是对象引用,需要自动装箱和拆箱,对基本类型操作时会稍有性能损失。 - 内存分配:数组创建时分配的内存是连续的,而
ArrayList虽然内部使用数组,但在扩容时可能会导致数据迁移,产生一定的性能开销。 - 使用场景:如果数据量固定且不大,且对性能要求极高,可以选择数组。反之,如果数据量动态变化,或需要频繁的增删操作,
ArrayList会是更好的选择。
List的判空判null,掌握几种方法,直接说代码实现?
- 直接判空判
null - 使用
CollectionUtils.isEmpty()(Apache Commons Collections) - 使用
Optional(alldata项目源码中还真有使用)
// 1、直接判空判null
if (list == null || list.isEmpty()) {
}
// 2、Apache Commons Collections
if (CollectionUtils.isEmpty(list)) {
}
// 3、jdk8以上
if (Optional.ofNullable(list).orElse(Collections.emptyList()).isEmpty()) {
}