0%

Java集合类-ArrayList

ArrayList如何扩容的?

无参构造函数中初始化一个空数组

调用add,往数组末尾添加元素前,会调用ensureCapacityInternal(size+1),第一次会创建DEFAULT_CAPACITY(值为10)长度的数组,再会调用grow(),新的数组长度为之前长度的1.5倍,接着调用Arrays.copyOf()将旧的数组元素复制到新数组中,底层实现是System.arraycopy()。

以上是自动扩容,也可以调用ensureCapacity(minCapacity)来手动扩容,需要手动扩容因为,如果提前知道数据量很大,就可以直接扩容到指定容量,自动扩容每次都会扩容原来的1.5倍,避免频繁拷贝数组的开销。

remove和clear后没有缩容,只会把数组中不需要的元素对应的位置设置为null,以便垃圾回收。

ArrayList查询元素的时间复杂度是多少?

数组访问任意位置元素的时间复杂度为O(1)

ArrayList插入元素的时间复杂度是多少?

插入末尾时间复杂度为O(1),因为有指针记录末尾的位置,有新元素直接填充末尾的空位,当末尾没有空位时,需要扩容,扩容为原来长度的1.5倍,扩容需要拷贝旧数组的数据到新数组,时间复杂度是O(n),所以插入元素到末尾的均摊时间复杂度是O(1)。

插入元素到中间位置的时间复杂度较高,因为要把插入位置后面所有元素后移一位,设数组长度为n,插入第1、2、3、… 、n-1、n个位置,分别需要移动元素的个数是n、n-1、n-2、… 、2、1,由等差数列求和公式得结果为n(n+1)/2,除以n得平均移动(n+1)/2个,所以插入到中间位置的平均时间复杂度是O(n)。

ArrayList删除元素的时间复杂度是多少?

与插入元素的情况一样,删除末尾元素时间复杂度为O(1),删除中间位置的元素要把该位置后面的元素都前移一位,平均时间复杂度为O(n)

ArrayList与LinkedList有何区别?

  1. ArrayList基于数组实现,容量不够时会动态扩容;LinkedList基于链表实现,实际是是一个双向链表,同时可以作为队列、栈使用
  2. ArrayList适合随机访问数据,时间复杂度O(1);LinkedList不适合随机访问数据,因为这会遍历链表,最坏情况下访问最后一个元素,要遍历整个链表,时间复杂度O(n)
  3. LinkedList适合插入和删除首尾元素,时间复杂度O(1);ArrayList插入和删除尾元素,其实是在数组的末尾填充元素,时间复杂度O(1),插入尾元素可能会触发扩容,扩容时会拷贝数组,时间复杂度会达到O(n),ArrayList插入和删除非末尾元素,需要移动操作,时间复杂度O(n)
  4. LinkedList需要占用更多的内存,因为每个结点除了存储元素数据外,还有额外的链表指针,ArrayList底层是数组,存储的直接是元素数据
  5. ArrayList对于CPU缓存命中率友好,因为存储空间连续,符合数据访问的空间局部性特点

如何求两个ArrayList的交集、并集、差集?

并集:addAll

交集:retainAll

差集:removeAll

ArrayList序列化有什么注意事项?

底层的elementData数组,可能没有填充满,所以不能直接序列化,会浪费空间,序列化时要先写入数组元素真实个数,再写入数组中实际存储的元素

Arrays.asList(T… a)有什么坑

  1. 传递一个原始数据类型的数组,例如int[],会只被当做一个元素,只能使用对象类型的数组
  2. 得到的list无法add、remove、clear,会抛出异常

ArrayList有什么坑?

List.remove()有两个。

一个public E remove(int index)

一个是public boolean remove(Object o)

容易调用不是预期的重载方法。

ArrayList单线程遍历然后删除元素会有什么问题?

索引错位。

用Iterartor的next迭代列表的时候,通过ArrayList的remove方法移除元素会抛出ConcurrentModificationException,因为迭代器迭代的时候,内部也是用一个int类型指针记录位置的,要是外面删除了元素,位置就错位了,迭代就不准了。

参考