关于 ArrayList 和 LinkedList 的比较,网上流传着很多不准确的说法。比如 “ArrayList 查询快,LinkedList 增删快”,这种说法过于笼统,不严谨,实际情况要复杂一些。下面从它们底层的数据结构和工作机制入手,按场景对比分析它们的性能差异。
影响 ArrayList 性能的关键点在于数组拷贝,动态扩容和随机增删元素都会进行数组拷贝。数组拷贝既消耗时间,又消耗空间,应尽量避免之。由数组拷贝机制可以推理得到 ArrayList 的一个特点,增删元素的位置越往后越快,因为元素位置越靠后,数组拷贝的规模越小。
影响 LinkedList 性能的关键点在于寻址和内存分配。LinkedList 寻址慢在于每 get 一个元素都需要查找, 即使有折半判断 + 双向查找优化,仍比不上 ArrayList 按“索引”取址. 因此对 LinkedList 来说,增删的元素位置越靠近两端,效率越高。 内存分配是说,向 LinkedList 增加或插入元素,每次都需要申请内存。这一点显然不如 ArrayList 的静态分配效率高(不考虑动态扩容)。
遍历
总体来讲,ArrayList 的效率比 LinkedList 稍高一些。
值得注意的是,要正确选择 for 循环和 Iterator:
- ArrayList 使用普通 for 循环遍历效率比较高,不要用 Iterator
- 由于 LinkedList 查找元素时寻址性能差,所以不要使用普通的 for 循环遍历 LinkedList, 应该使用 Iterator.
- 关于 foreach, java 不同版本的编译器可能会对 foreach 做一些优化,需要根据实际测试结果判断
尾部增加元素
总体来讲,选择 ArrayList 比较好,但要注意,应尽量初始化 capacity,以避免发生动态扩容。
- ArrayList 基于数组,寻址很快,在末尾位置赋值即可;
- LinkedList 虽然寻址较慢,但在末尾插入元素,JDK做了优化,会从末尾查找,所以末尾插入元素,两者在寻址方面实际上效率差别不大。主要差别在于,LinkedList 插入时要新建节点对象,向 JVM 申请内存空间。
有些测试结果可能会显示,末尾增加元素时,LinkedList 性能优于 ArrayList. 这一点跟 ArrayList 动态扩容和集合内元素数量有关系。总的来讲,不较真的情况下,选用 ArrayList 是 OK 的。
尾部删除元素
如果总是从尾部删除元素,理论上 ArrayList 和 LinkedList 差别不大。ArrayList 将末尾位置指定为 null,等待垃圾回收,无需数组拷贝;LinkedList 使用优化的寻址方法从末尾查找,对元素执行 unlink. 总体来讲,建议还是使用 ArrayList.
增删靠前位置的元素
根据上面的分析,增删元素的位置越靠前,ArrayList 拷贝数组的元素越多,而 LinkedList 寻址的消耗越小,因此在这个场景 LinkedList 具有无可争议的优势。
性能测试
ArrayList 和 LinkedList 的性能之比较有很多影响因素,不能简单一概而论,与 ArrayList 动态扩容、集合元素数量、内存分配状况、垃圾回收等都有关系。最稳妥的办法是进行性能测试。