List 源码会问哪些面试题

本文深入剖析ArrayList的面试常见问题,包括ArrayList的扩容机制、删除元素的影响以及线程安全问题。详细解释了ArrayList在添加元素时如何自动扩容,删除元素的注意事项,以及在多线程环境下可能遇到的问题。同时对比ArrayList与LinkedList在性能和应用场景上的差异。
摘要由CSDN通过智能技术生成

List 源码会问哪些面试题

说说你自己对 ArrayList 的理解

很多面试官喜欢这样子开头,考察面试者对 ArrayList 有没有总结经验,介于 ArrayList 内容很多,建议先回答总体架构,再从某个细节出发作为突破口,比如这样:

ArrayList 底层数据结构是个数组,是有序的集合并且可以重复,支持null值,支持随机读写能力,是非线程安全的。其 API 都做了一层对数组底层访问的封装,ArrayList 集合在调用无参构造方法实例化的时候,并没有创建数组,而是在用户第一次执行添加的时候进行创建数组,ArrayList 的 add(),先进行判断数组是否已创建,如果没创建先创建数组(默认大小为10),如果数组容量不够了,add的时候会调用扩容方法grow,每次扩容的大小为 原大小+(原大小>>1) …

扩容类问题

使用 ArrayList 无参构造器,add 一个值进去,此时数组的大小是多少,下一次扩容前最大可用大小是多少?

使用无参构造器,此时数组为空,add 一个值,数组大小为 1 ,下次扩容前最大可用大小为 10

如果我连续往 list 里面新增值,增加到第 11 个的时候,数组的大小是多少?

增加到 11 的时候,此时希望数组的最小容量(minCapacity)为 11,需要扩容,扩容的公式为 newCapacity = oldCapacity + (oldCapacity >> 1) 即 10 + 10/2 = 15,数组会扩容为15。

数组初始化,加入一个值后,如果我使用 addAll 方法,一下子加入 15 个值,那么最终数组的大小是多少

加入第一个值后,数组最大容量为 10 ,addAll 加入 15个,期望数组容量为 16,需要扩容,扩容公式 10 + 10/2 = 15 ,仍达不到我们期望的大小,此时扩容 grow() 方法里有个判断,``

if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

最终扩容后值为 16。

现在有一个很大的数组需要拷贝,原数组大小是 5k,请问如何快速拷贝?

因为原数组比较大,如果新建新数组的时候,不指定数组大小的话,就会频繁扩容,频繁扩容就会有大量拷贝的工作,造成拷贝的性能低下,所以回答说新建数组时,指定新数组的大小为 5k 即可。

为什么说扩容会消耗性能?

扩容底层使用的是 System.arraycopy 方法,会把原数组的数据全部拷贝到新数组上,所以性能消耗比较严重。

源码扩容过程有什么值得借鉴的地方?

  • 扩容的思想值得学习,通过自动扩容的方式,让使用者不用关心底层数据结构的变化,封装得很好,1.5 倍的扩容速度,可以让扩容速度在前期缓慢上升,在后期增速较快,大部分工作中要求数组的值并不是很大,所以前期增长缓慢有利于节省资源,在后期增速较快时,也可快速扩容。
  • 扩容过程中,有数组大小溢出的意识,比如要求扩容后的数组大小,不能小于 0,不能大于 Integer 的最大值。

删除类问题

有一个 ArrayList,数据是 2、3、3、3、4,中间有三个 3,现在我通过 for (int i=0;i<list.size ();i++) 的方式,想把值是 3 的元素删除,请问可以删除干净么?最终删除的结果是什么,为什么?删除代码如下:

List<String> list = new ArrayList<String>() {{
  add("2");
  add("3");
  add("3");
  add("3");
  add("4");
}};
for (int i = 0; i < list.size(); i++) {
  if (list.get(i).equals("3")) {
    list.remove(i);
  }
}

不能删除干净,最终删除的结果是 2、3、4,有一个 3 删除不掉,因为每次删除元素之后,数组会往前挪动,而此时循环的 i 在不断地增长

image.png

还是上面的 ArrayList 数组,通过增强 for 循环进行删除,可以么?

不可以,会报错。因为增强 for 循环过程其实调用的就是迭代器的 next () 方法,当你调用 list.remove () 方法进行删除时,modCount 的值会 +1,而这时候迭代器中的 expectedModCount 的值却没有变,导致在迭代器下次执行 next () 方法时,expectedModCount != modCount 就会报 ConcurrentModificationException 的错误。

还是上面的数组,如果删除时使用 Iterator.remove () 方法可以删除么,为什么?

可以的,因为 Iterator.remove () 方法在执行的过程中,会把最新的 modCount 赋值给 expectedModCount,这样在下次循环过程中,modCount 和 expectedModCount 两者就会相等。

以上三个问题对于 LinkedList 也是同样的结果么?

是的,虽然 LinkedList 底层结构是双向链表,但对于上述三个问题,结果和 ArrayList 是一致的。

ArrayList 和 LinkedList 有何不同?

  • 底层数据结构:

    • ArrayList 底层是数组
    • LinkedList 底层是双向链表
  • 新增的实现:

    • ArrayList 会先计算并决定是否扩容,然后把新增的数据直接赋值到数组上

    • LinkedList 仅仅只需要改变插入节点和其前后节点的指向位置关系即可

  • 删除的实现:

    • ArrayList 将制定节点删除后,指定节点之后的节点需要向前移动
    • LinkedList 仅仅只是改变删除节点及其前后节点指向关系即可
  • 查询的实现:

    • ArrayList 只需要通过瞎编就能找到对应节点,支持快速随机访问
    • LinkedList 需要遍历查找(使用二分折半查找)
  • 容量:

    • ArrayList 有最大容量,为 Integer 的最大值,大于这个值 JVM 不会为数组分配内存空间
    • LinkedList 底层是双向链表理论上可以无限大,但在源码中,LinkedList 实际大小用的是 int 类型,所以 LinkedList 也不能超过 Integer 的最大值,不然会溢出

ArrayList 和 LinkedList 应用场景

  • ArrayList 更适合快速查找匹配,不适合频繁新增删除
  • LinkedList 更适合经常新增和删除,查询少的场景

ArrayList 和 LinedList 是线程安全的么,为什么?

当两者作为非共享变量时,比如说仅仅是在方法里面的局部变量时,是没有线程安全问题的,只有当两者是共享变量时,才会有线程安全问题。主要的问题点在于多线程环境下,所有线程任何时刻都可对数组和链表进行操作,这会导致值被覆盖,甚至混乱的情况。

如果有线程安全问题,在迭代的过程中,会频繁报 ConcurrentModificationException 的错误,意思是在当前循环的过程中,数组或链表的结构被其它线程修改了。

如何解决线程安全问题?

Java 源码中推荐使用 Collections#synchronizedList 进行解决,Collections.synchronizedList 的返回值是 List 的每个方法都加了 synchronized 锁,保证了在同一时刻,数组和链表只会被一个线程所修改,或者采用 CopyOnWriteArrayList 并发 List 来解决

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值