相信不少小伙伴在查看 ArrayList
集合时发现该集合类实现了一个 RandomAccess
空实现的接口,源码如下 ⬇️
ArrayList 源码:
RandomAccess 接口源码:
可以看到 RandomAccess
确实是一个空接口,如果知道 Serializable
接口,那么大概也就知道了该接口或许也是一个标记作用,一个类通过实现 Serializable
接口标记该类支持序列化功能 。带着猜测我们先来看一看 JDK 官方文档对于该接口的解释:
大致意思:
List
实现使用的 标记接口,表示它们 支持快速(通常是常数时间)随机访问。该接口的主要目的是允许泛型算法在应用于随机或顺序访问列表时改变其行为以提供良好的性能。
通过官方文档的说明,我们知道如果一个 List
的子类实现了 RandomAccess
接口,则表示该类支持快速随机访问集合类中的元素。
这不难联想到数组,数组能够通过索引 index
进行快速访问,那么再联想到 ArrayList
底层本身便是通过数组实现的,相关源码如下:
ArrayList
同样是通过索引进行元素访问,只不过相比于数组的访问方式,其需要通过 get(int index)
方法进行访问。相关源码如下:
public E get(int index) {
// 判断下标范围是否越界
rangeCheck(index);
// 返回对应下标位置元素
return elementData(index);
}
E elementData(int index) {
// 返回 index 位置处的元素
return (E) elementData[index];
}
对比于 LinkedList
底层由链表实现,该类并没有实现 RandomAccess
接口,部分源码如下:
到这里,问题也就即将得到解答了,我们先看看 ArrayList
与 Linked
两个集合类的对比。
- ArrayList:底层由数组实现,查询速度快,通过下标直接访问数组元素;增删速度慢,不考虑最优情况下每次增删元素需要进行数组元素移动。
- LinkedList:底层由链表实现,查询速度慢,其为顺序访问,不考虑最优情况每次都需要从头节点开始遍历元素;增删元素快,只需要修改相应指针指向即可。
基于上面的对比,也就不难看出,RandomAccess
接口就是用来标记能够随机访问的集合,即底层由数组实现的集合。如此一来,在需要进行集合遍历时,我们便可以通过对不同集合的底层实现来选择不同的遍历方式,从而提升性能。
得出结论:
随机访问使用循环遍历,顺序访问则使用迭代器遍历。
下面进行大量数据场景下的性能测试:
public class Test {
// 使用 for 循环遍历(实现 RandomAccess 接口)
public static long forTraversal(List list){
long startTine = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
long endTime = System.currentTimeMillis();
return endTime - startTine;
}
// 使用迭代器遍历(不实现 RandomAccess 接口)
public static long iteratorTraversal(List list){
long startTine = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();
}
long endTime = System.currentTimeMillis();
return endTime - startTine;
}
public static void main( String[] args ) {
// 测试 ArrayList
List<Integer> arrList = new ArrayList<>();
// 加入数据
for (int i = 0; i < 100000; i++) {
arrList.add(i);
}
// 测试耗时
long forTime = forTraversal(arrList);
long iterTime = iteratorTraversal(arrList);
System.out.println("ArrayList 测试结果:");
System.out.println("for 循环遍历时间:" + forTime);
System.out.println("迭代器遍历时间:" + iterTime);
// 测试 LinkedList
List<Integer> linkedList = new LinkedList<>();
// 加入数据
for (int j = 0; j < 100000; j++) {
linkedList.add(j);
}
// 测试耗时
long forTime1 = forTraversal(linkedList);
long iterTime1 = iteratorTraversal(linkedList);
System.out.println("LinkedList 测试结果:");
System.out.println("for 循环遍历时间:" + forTime1);
System.out.println("迭代器遍历时间:" + iterTime1);
}
}
测试结果:
ArrayList 测试结果:
for 循环遍历时间:2
迭代器遍历时间:4
ArrayList 测试结果:
for 循环遍历时间:3972
迭代器遍历时间:4
得出结论:
- 支持随机访问的集合使用 for 循环遍历效率优于迭代器;
- 支持顺序访问的集合使用迭代器遍历效率优于 for 循环遍历;
- 在进行集合遍历前可通过
instanceof RandomAccess
进行提前判断,从而选择不同的遍历方式,提升效率。