不知道大家在平时的开发中有没有注意到过这个接口,这个接口是一个标志性接口,和Cloneable接口一样,本身没有提供任何接口方法。任何实现RandomAccess接口的对象都可以认为支持快速随机访问的对象。
比如看一下我们最常用的集合类ArrayList和LinkedList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
ArrayList是一个有序集合,顶层实现是基于数组的,LinkedList底层实现是基于双向链表的,来看下如下的测试:
假设我有一个接口,其中的方法入参是一个List,我并不关注这个List具体是什么,我内部就是用for循环去读取它的数据
public class RandomAccessShow { public void show(List<Integer> showList){ if(showList != null){ int s = showList.size(); for(int k = 0; k < 100;k++){ for(int i = 0;i < s;i++){ showList.get(i); } } } } }
现在调用上面这个show方法,并统计下耗时情况,传参类型是分别为ArrayList和LinkedList
public class Test { public static void main(String[] args){ //初始化集合 List<Integer> list = new ArrayList<Integer>(); //list = new LinkedList<Integer>(); for(int i = 0;i<10000;i++){ list.add(i); } long bt = System.currentTimeMillis(); RandomAccessShow test = new RandomAccessShow(); test.show(list); long et = System.currentTimeMillis(); System.out.println(et-bt); } }
运行上面的main方法,ArrayList和LinkedList的耗时分别为11ms和5171ms
修改下show方法,根据传入集合是否实现了RandomAccess接口来选择不同的遍历方式
public class RandomAccessShow { public void show(List<Integer> showList){ if(showList != null){ if(showList instanceof RandomAccess){ int s = showList.size(); for(int k = 0; k < 100;k++){ for(int i = 0;i < s;i++){ showList.get(i); } } }else { Iterator<Integer> it = showList.iterator(); for(int k = 0; k < 100;k++){ while (it.hasNext()){ it.next(); } } } } } }
运行上面的main方法,ArrayList和LinkedList的耗时分别为11ms和3ms
理解RandomAccess接口可以帮助我们在应用程序中知道正在处理的List是否可以支持快速随机访问,从而能提高程序的性能。
LinkedList的for循环为啥这么耗时呢?这其实跟数据结构有关系,看下LinkedList的get方法如下,其实调用的是node方法:
public E get(int index) { checkElementIndex(index); return node(index).item; } /** * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
来分析下上面的node方法,首先跟军index的位置判断需要正序遍历还是倒序遍历(这里已经做了一次优化了),然后就是for循环查找,这里就是遍历慢的原因所在
LinkedList的for循环的取值过程如下:
1.get(0),拿到0位的Node<E>中的数据
2.get(1),先拿到0位的Node<E>中的数据,然后再根据0位拿到1位中Node<E>中的数据,
3.get(2),先拿到0位的Node<E>中的数据,然后再根据0位拿到1位中Node<E>中的数据,然后再根据1位拿到2位中Node<E>的数据
4.以此类推
可以看出LinkedList在get任何一个位置的数据时,都会把Node<E>中的index前面的数据过一遍,如果集合中有10条数据,LinkedList的遍历次数是1+2+3+4+5+5+4+3+2+1=30次,而ArrayList只需要遍历10次
LinkedList的Iterator的遍历为什么很快呢?
public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; }
因为next的存在,不需要在遍历前面的节点了