小白学Java:奇怪的RandomAccess

小白学Java:奇怪的RandomAccess

咱们以前在分析那三个集合源码的时候,曾经说到:ArrayList和Vector继承了RandomAccess接口,可是LinkedList并无,咱们还知道继承了这个接口,就意味着其中元素支持快速随机访问(fast random access)数组

RandomAccess是个啥

出于好奇,我特地去查看了RandomAccess的官方文档,让我以为异常惊讶的是!这个接口中啥也没有!是真的奇怪!(事实上和它相似的还有Cloneablejava.io.Serializable,这俩以后会探讨)只留下一串冰冷的英文。app

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.dom

哎,无论他,翻译就完事了,今天的生活也是斗志满满的搬运工生活呢!ide

我用我本身的语言组织一下:工具

  • 它是个啥呢?这个接口自己只是一个标记接口,因此没有方法也是情有可原的。
  • 标记啥呢?它用来做为List接口的实现类们是否支持快速随机访问的标志,这样的访问一般只须要常数的时间。
  • 为了啥呢?在访问列表时,根据它们是不是RandomAccess的标记,来选择访问他们的方法以提高性能。

咱们知道,ArrayList和Vector底层基于数组实现,内存中占据连续的存储空间,每一个元素的下标实际上是偏移首地址的偏移量,这样子查询元素只须要根据:元素地址 = 首地址+(元素长度*下标),就能够迅速完成查询,一般只须要花费常数的时间,因此它们理应实现该接口。可是链表不一样,链表依据不一样节点之间的地址相互引用完成联系,自己不要求地址连续,查询的时候须要遍历的过程,这样子会致使,在数据量比较大的时候,查询元素消耗的时间会很长。oop

RandomAccess接口的全部实现类:
ArrayList, AttributeList, CopyOnWriteArrayList, RoleList, RoleUnresolvedList, Stack, Vector性能

the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.学习

能够经过xxList instanceof RandomAccess)判断该列表是否为该接口的实例,若是是顺序访问的列表(如LinkedList),就不该该经过下标索引的方式去查询其中的元素,这样效率会很低。测试

/*for循环遍历*/
for (int i=0, n=list.size(); i < n; i++)
    list.get(i);
/*Iterator遍历*/
for (Iterator i=list.iterator(); i.hasNext();)
    i.next();

对于实现RandomAccess接口,支持快速随机访问的列表来讲,for循环+下标索引遍历的方式比迭代器遍历的方式要更快。

forLoop与Iterator的区别

对此,咱们是否能够猜测,若是是LinkedList这样并不支持随即快速访问的列表,是不是Iterator更快呢?因而咱们进行一波尝试:

  • 定义关于for循环和Iterator的测试方法
/*for循环遍历的测试*/
    public static void forTest(List list){
        long start = System.currentTimeMillis();
        for (int i = 0,n=list.size(); i < n; i++) {
            list.get(i);
        }
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println(list.getClass()+" for循环遍历测试 cost:"+time);
    }
    /*Iterator遍历的测试*/
    public static void iteratorTest(List list){
        long start = System.currentTimeMillis();
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            iterator.next();
        }
        long end = System.currentTimeMillis();
        long time = end-start;
        System.out.println(list.getClass()+"迭代器遍历测试 cost:"+time);
    }
  • 测试以下
public static void main(String[] args) {    
        List<Integer> linkedList = new LinkedList<>();
        List<Integer> arrayList = new ArrayList<>();
        /*ArrayList不得不加大数量观察它们的区别,其实差异不大*/
        for (int i = 0; i < 5000000; i++) {
            arrayList.add(i);
        }
        /*LinkedList 这个量级就能够体现比较明显的区别*/
        for(int i = 0;i<50000;i++){
            linkedList.add(i);
        }
        /*方法调用*/
        forTest(arrayList);
        iteratorTest(arrayList);
        forTest(linkedList);
        iteratorTest(linkedList);
    }
  • 测试效果想当的明显

咱们能够发现:

  • 对于支持随机访问的列表(如ArrayList),for循环+下标索引的方式和迭代器循环遍历的方式访问数组元素,差异不是很大,在加大数量时,for循环遍历的方式更快一些。
  • 对于不支持随机访问的列表(如LinkedList),两种方式就至关明显了,用for循环+下标索引是至关的慢,由于其每一个元素存储的地址并不连续。
  • 综上,若是列表并不支持快速随机访问,访问其元素时,建议使用迭代器;若支持,则可使用for循环+下标索引。

判断是否为RandomAccess

上面也提到了, 这个空空的接口就是承担着标记的职责(Marker),标记着是否支持随机快速访问,若是不支持的话,还使用索引来遍历的话,效率至关之低。既然有标记,那咱们必定有方法去区分标记。这时,咱们须要使用instanceof关键字帮助咱们作区分,以选择正确的访问方式。

public static void display(List<?> list){
    if(list instanceof RandomAccess){
        //若是支持快速随机访问
        forTest(list);
    }else {
        //不支持快速随机访问,就用迭代器
        iteratorTest(list);
    }
}

再进行一波测试看看,他俩都找到了本身的归宿:

事实上,集合工具类Collections中有许多操做集合的方法,咱们随便举一个从前日后填充集合的方法:

public static <T> void fill(List<? super T> list, T obj) {
        int size = list.size();
            
        if (size < FILL_THRESHOLD || list instanceof RandomAccess) {
            //for遍历
            for (int i=0; i<size; i++)
                list.set(i, obj);
        } else {
            //迭代器遍历
            ListIterator<? super T> itr = list.listIterator();
            for (int i=0; i<size; i++) {
                itr.next();
                itr.set(obj);
            }
        }
    }

还有许多这样的方法,里面有许多值得学习的地方。我是一个正在学习Java的小白,也许个人知识还未有深度,可是我会努力把本身学习到的作一个体面的总结。对了,若是文章有理解错误,或叙述不清之处,还望你们评论区批评指正。

参考资料:JDK1.8官方文档

相关文章
相关标签/搜索