前言
Java集合部分的题目,是我根据Java Guide的面试突击版本V3.0再整理出来的,其中,我选择了一些比较重要的问题,并重新做出相应回答,希望对大家起到一定的帮助。
Java集合
- 说说Arraylist 与 LinkedList 区别?
- 底层数据结构:ArrayList是数组,LinkedList是链表,那么问题就变成了数组和链表这两种数据结构的区别
- 增删、查的平均时间复杂度:
- 插入和删除(注意,这里的插入和删除并不包括查找,仅仅看操作本身的时间复杂度。另外,下面讨论的前提是需要维护数组有序。)
- 数组如果插(删除)在尾部,这种情况是最优时间复杂度,为O(1),如果插(删除)在头部,这种情况是最差时间复杂度,为O(N);平均时间复杂度为 (1+2+3+...n)n/2 = O(N)。
- 注意:如果数组是无序的,完全可以把待插入数据和插入位置的数据交换一下,实现O(1)的时间复杂度。
- 链表插入(删除)的时间复杂度,最好最差和平均都是O(1)
- 数组如果插(删除)在尾部,这种情况是最优时间复杂度,为O(1),如果插(删除)在头部,这种情况是最差时间复杂度,为O(N);平均时间复杂度为 (1+2+3+...n)n/2 = O(N)。
- 查找
- 数组根据下标的随机查找,最好最坏平均都是O(1)
- 链表根据下标的随机查找,最好是O(1),最坏是O(N),平均为O(N)
- JAVA实现中优化了链表根据下标查找的过程,先判断待查找的索引下标是在前半段还是后半段,如果在前半段,就从头开始查;如果在后半段,就从尾开始查。
- 数组和链表,根据值查找,都需要遍历,时间复杂度都为O(N)
- 插入和删除(注意,这里的插入和删除并不包括查找,仅仅看操作本身的时间复杂度。另外,下面讨论的前提是需要维护数组有序。)
- 空间复杂度:
- 链表需要额外存储指针数据,数组不需要,但是JAVA实现中会冗余一些数组位置,也增加了空间复杂度。
- 说⼀说 ArrayList 的扩容机制
- 扩容原理:ArrayList的add方法,在真正add之前,会检查是否需要扩容,扩容的关键就是确定新的数组长度,java源码中是按照原始长度的1.5倍来确定新长度的。具体的拷贝过程,就是建立一个新的长度的数组,把原始数组中的数据拷贝到新数组中
- 最佳实践:在已知需要加入list中的元素个数时,可以调用ensureCapacity方法来提前扩容到指定的大小,避免添加的过程中不断扩容,提高性能
- 说一说HashMap的底层数据结构
- JDK1.8之前,HashMap的底层数据结构是数组+链表
- JDK1.8之后,Java优化了HashMap的链表部分,当链表达到默认阈值8时,会转变为红黑树。
- HashMap的扩容机制
- 和ArrayList类似,初始都是一个空的数组,当实际添加数据时,会初始化一个默认长度为16的数组
- 负载因子:HashMap的扩容依据 是当当前容器中的键值对个数 大于 数组长度×负载因子=负载个数时,进行扩容,扩容为之前长度的2倍。
- HashMap的长度为什么是2的幂次方
- hash值是int类型的,默认有很大的范围,但是数组又不可能那么大,所以需要按照数组的实际大小取模
- 位运算的效率高于取模的效率,所以要尽可能转化为位运算。
- 当长度为2的幂次方时,h%length取模的操作可以转化为h&(length-1)
- HashMap 多线程操作导致死循环问题:这个问题的关键就是在rehash的过程中,多线程导致环形链表。HashMap本身就是线程不安全的,多线程的情况下应该使用ConcurrentHashMap。
- ConcurrentHashMap的并发处理
- 我查阅了1.8的源码,在key或者value为null的时候,会抛出空指针异常。
- 在链表头部加锁,和1.7版本的分段锁相比,锁的粒度更细,并发度更高
网友评论