每日面试题打卡(容器篇)——Day9

博主个人博客网站:文客
这个系列会长期更新!
如果你想每天和我打卡面试题、交流技术,可以关注一下我的个人博客网站:文客,我会每天在这里更新技术文章和面试题,也会及时收到大家的评论与留言,欢迎各位大佬来交流!

ArrayList的扩容机制

ArrayList有三个构造函数。默认的无参构造函数会返回一个空的数组。带初始容量的构造参数,返回对应长度的数组,如果参数值为0则返回空数组。包含特定集合元素的构造函数,将传入的集合转换为数组,通过Arrays.copyOf方法把集合中的元素拷贝到elementData中。

我们以无参构造函数构造的ArrayList为例。

扩容操作开始于向集合中添加元素,对应着ArrayList中的两个方法:add(E e)和add(int index, E element)方法,可以看出一个是默认的尾插,一个是指定位置插入。这两个方法的共同点就是都调用了ensureCapacityInternal(size + 1),这也是扩容操作的入口,这个方法会调用calculateCapacity方法,这个方法首先会判断当前数组是否为默认的空数组,如果是的话返回默认容量和minCapacity的最大值,否则返回minCapacity。然后会继续进入另一个方法,首先会将modCount+1(这里对应Java的fail-fast机制,下面会说),然后会判断数组容量和当前数组长度的大小,如果修改后的数组容量更大的话就需要扩容操作,扩容操作的核心方法是grow方法,下面针对三种情况进行解析:

  1. 当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10)那么根据下面逻辑可以看到最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;
  2. 当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1那么根据下面逻辑可以看到最后数组的容量会从0变成1。这边可以看到一个严重的问题,一旦我们执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。
  3. 当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)。

fail-fast机制:fail-fast机制其实是Java的一种错误检测机制,当多个线程涉及到对集合结构上的修改时,就可能会触发fail-fast机制。比如说现在有两个线程A和B,A使用iterator遍历集合中的元素,在线程A遍历中的某个时刻,线程B移除了集合中的某个元素,此时就会抛出ConcurrentModificationException,也就是并发修改异常。在遍历集合的过程中,会直接访问集合中的内容并且维护一个modCount遍历,当对集合进行修改操作时,肯定会修改modCount的值,每当迭代器遍历一个元素时,都会检测当前的modCount是不是expectedmodCount,如果是则继续遍历,如果不是则抛出并发修改异常。

Queue与Deque的区别

Queue扩展了Collection接口,它是单端队列,只能从一端插入元素一端移除元素,符合FIFO的原则。

Deque扩展了Queue接口,它是双端队列,两端都可以插入和移除元素,所以Deque也可以模拟栈。

ArrayDeque与LinkedList的区别

首先它们都实现了Deque接口,所以都具备双端队列的能力。那么它们有什么区别呢?

  • 从底层数据结构来看,ArrayDeque底层是通过可变长的数组和双指针实现的,而LinkedList则是通过双链表实现的。
  • ArrayDeque不支持存储NULL数据,而LinkedList支持
  • ArrayDeque在容量不足时会有扩容机制,一般是扩容到原来的两倍,而LinkedList没有扩容机制。

说一说PriorityQueue

PriorityQueue是JDK1.5被引入的,它与其它队列的区别是它是根据优先级来排序的,即总是优先级高的FIFO。

  • PriorityQueue利用二叉堆的数据结构来实现的,底层使可变长的数组来存储数据
  • PriorityQueue通过上浮和下沉的方式,实现了O(logn)的时间复杂度插入和删除堆顶元素
  • PriorityQueue是非线程安全的
  • PriorityQueue默认是小顶堆,它的构造函数可以接收一个Comparator作为参数,可以自定义队列的优先级

BlockingQueue是什么?

这个题严格意义上来说,更贴近于多线程的面试题,后面到多线程时还会再次收录这个题。

阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。

  1. 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,知道队列不满
  2. 支持阻塞的移除方法:意思是当队列空时,获取元素的线程会等待队列变为非空

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

我们都知道,当队列不可用时,Queue提供了两种处理方式:返回特殊值和抛出异常。在阻塞队列不可用时,BlockingQueue提供了四种处理方式:抛出异常、返回特殊值、一直阻塞、超时退出。

在 Queue 中 poll()和 remove()有什么区别?

在队列不可用时,Queue提供了两种处理方式:返回特殊值和抛出异常。

我们梳理一下返回特殊值的方法:add()、remove()、element()。

再梳理一下抛出异常的方法:offer()、pop()、peek()。

所以如果没有元素时,remove方法会返回null,而poll方法会抛出NoSuchElementException

下期预告

Map接口之前的先告一段落啦,后几天会集中更新Map的面试题(HashMap可是重头戏)!!

另外博主最近在复习MySQL,可能会整理出一些干货知识,感兴趣的朋友可以关注一波。

博客原文地址

每日面试题打卡(容器篇)——Day9
在这里插入图片描述

你可能感兴趣的:(面试题打卡,面试,java,容器)