kaka的gravatar头像
kaka 2018-03-21 11:26:46
java RandomAccess接口说明

不知道大家在平时的开发中有没有注意到过这个接口,这个接口是一个标志性接口,和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的存在,不需要在遍历前面的节点了


打赏

已有1人打赏

最代码官方的gravatar头像
最近浏览
Joachiming  LV1 2020年6月2日
851405506  LV7 2018年11月14日
zyl  LV34 2018年10月11日
ylife87  LV12 2018年8月14日
zhos0212  LV19 2018年5月8日
微微上翘  LV23 2018年4月28日
hoictas 2018年4月24日
暂无贡献等级
sinyuwl  LV3 2018年4月17日
低调人  LV38 2018年4月17日
linking  LV13 2018年4月12日
顶部 客服 微信二维码 底部
>扫描二维码关注最代码为好友扫描二维码关注最代码为好友