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有何区别?
- ArrayList基于数组实现,容量不够时会动态扩容;LinkedList基于链表实现,实际是是一个双向链表,同时可以作为队列、栈使用
- ArrayList适合随机访问数据,时间复杂度O(1);LinkedList不适合随机访问数据,因为这会遍历链表,最坏情况下访问最后一个元素,要遍历整个链表,时间复杂度O(n)
- LinkedList适合插入和删除首尾元素,时间复杂度O(1);ArrayList插入和删除尾元素,其实是在数组的末尾填充元素,时间复杂度O(1),插入尾元素可能会触发扩容,扩容时会拷贝数组,时间复杂度会达到O(n),ArrayList插入和删除非末尾元素,需要移动操作,时间复杂度O(n)
- LinkedList需要占用更多的内存,因为每个结点除了存储元素数据外,还有额外的链表指针,ArrayList底层是数组,存储的直接是元素数据
- ArrayList对于CPU缓存命中率友好,因为存储空间连续,符合数据访问的空间局部性特点
如何求两个ArrayList的交集、并集、差集?
并集:addAll
交集:retainAll
差集:removeAll
ArrayList序列化有什么注意事项?
底层的elementData数组,可能没有填充满,所以不能直接序列化,会浪费空间,序列化时要先写入数组元素真实个数,再写入数组中实际存储的元素
Arrays.asList(T… a)有什么坑
- 传递一个原始数据类型的数组,例如int[],会只被当做一个元素,只能使用对象类型的数组
- 得到的list无法add、remove、clear,会抛出异常
ArrayList有什么坑?
List.remove()有两个。
一个public E remove(int index)
一个是public boolean remove(Object o)
容易调用不是预期的重载方法。
ArrayList单线程遍历然后删除元素会有什么问题?
索引错位。
用Iterartor的next迭代列表的时候,通过ArrayList的remove方法移除元素会抛出ConcurrentModificationException,因为迭代器迭代的时候,内部也是用一个int类型指针记录位置的,要是外面删除了元素,位置就错位了,迭代就不准了。