文章目录
- 2.1. Arraylist 和 Vector 的区别?
- 2.2. Arraylist 与 LinkedList 区别?
- 2.2.1. 补充内容:双向链表和双向循环链表
- 2.2.2. 补充内容:RandomAccess 接口
- 2.3 ArrayList 的扩容机制
2.1. Arraylist 和 Vector 的区别?
ArrayList
是List
的主要实现类,底层使用Object[ ]
存储,适用于频繁的查找工作,线程不安全 ;Vector
是List
的古老实现类,底层使用Object[ ]
存储,线程安全的。
2.2. Arraylist 与 LinkedList 区别?
- 是否保证线程安全:
ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全; - 底层数据结构:
Arraylist
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) - 插入和删除是否受元素位置的影响: ①
ArrayList
采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)
方法的时候,ArrayList
会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ②LinkedList
采用链表存储,所以对于add(E e)
方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i
插入和删除元素的话((add(int index, E element)
) 时间复杂度近似为o(n))
因为需要先移动到指定位置再插入。 - 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。 - 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
2.2.1. 补充内容:双向链表和双向循环链表
双向链表: 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
另外推荐一篇把双向链表讲清楚的文章:https://juejin.im/post/5b5d1a9af265da0f47352f14
双向循环链表: 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
2.2.2. 补充内容:RandomAccess 接口
public interface RandomAccess {
}
查看源码我们发现实际上 RandomAccess
接口中什么都没有定义。所以,在我看来 RandomAccess
接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
在 binarySearch()
方法中,它要判断传入的 list 是否 RamdomAccess
的实例,如果是,调用indexedBinarySearch()
方法,如果不是,那么调用iteratorBinarySearch()
方法
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
ArrayList
实现了 RandomAccess
接口, 而 LinkedList
没有实现。为什么呢?我觉得还是和底层数据结构有关!ArrayList
底层是数组,而 LinkedList
底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArrayList
实现了 RandomAccess
接口,就表明了他具有快速随机访问功能。 RandomAccess
接口只是标识,并不是说 ArrayList
实现 RandomAccess
接口才具有快速随机访问功能的!
2.3 ArrayList 的扩容机制
ArrayList 是基于数组实现的动态数组,它提供了一种可以动态增长和缩减的数组结构。在 ArrayList 中,当数组存储的元素个数达到上限时,会触发扩容操作,以保证其容量能够存储更多的元素。
ArrayList 扩容机制的主要过程:
- 在添加元素时,首先会判断数组是否已满。如果已满,则需要进行扩容操作;
- 扩容操作会创建一个新的大数组,并将原数组中的元素全部复制到新数组中;
- 新数组的长度通常是原数组长度的 1.5 倍(可以通过源码中的
DEFAULT_CAPACITY_INCREMENT = 12 和 DEFAULT_CAPACITY = 10
来了解扩容因子)。如果指定了初始化容量,那么新数组的长度就是指定的容量大小; - 复制完元素后,会将指向原数组的引用更新为指向新数组的引用。
源码分析
private void grow(int minCapacity) {
// 获取当前数组容量
int oldCapacity = elementData.length;
// 扩容因子,以 1.5 倍方式进行扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量小于 minCapacity,则使用 minCapacity 作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量超出了最大容量,则抛出 OutOfMemoryError 异常
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制旧数组中的元素到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
由于每次扩容都需要重新进行数据复制,所以过于频繁的扩容操作会导致性能损失。为避免频繁的扩容操作,初始化 ArrayList
对象时可以指定其容量大小,或者使用 ensureCapacity(int minCapacity)
方法预先设置其容量大小,以提高效率。同时,在添加大量元素时,也可以使用 addAll(Collection<? extends E> c)
或 addAll(int index, Collection<? extends E> c)
方法,一次性添加多个元素,以减少扩容操作的次数。