JAVA容器之List

来源:互联网 发布:apache网站服务器架设 编辑:程序博客网 时间:2024/06/11 18:41

List的实现类主要是ArrayList和LinkedList,两个主要的差别是ArrayList是通过数组实现的,但LinkedList是通过链表实现。
可以想象,ArrayList在随机访问效率上远高于LinkedList,因为LinkedList访问一个元素必须从头节点开始依次访问知道找到目标节点,所以时间复杂度为O(n),而ArrayList的随机访问复杂度几乎是O(1)。
但频繁进行插入删除数据的时候,LinkedList的效率远高于ArrayList,因为LinedList只需要改变指针(引用)的内容,时间复杂度为O(1),而ArrayList添加与删除需要大量移动元素,时间复杂度为O(n)。

遍历效率分析

public class ListTest {    List<Integer> array = new ArrayList<Integer>();    List<Integer> link = new LinkedList<Integer>();    private final int size;    private long start, end;    public ListTest(int size) {        this.size = size;        for (int i = 0; i < size; i++) {            int t = (int) (Math.random() * 1000);            array.add(t);            link.add(t);        }    }    public void ArrayIteratorTraversal() {        Iterator<Integer> i = array.iterator();        int t;        start = System.currentTimeMillis();        while (i.hasNext()) {            t = i.next();        }        end = System.currentTimeMillis();        System.out.println("ArrayList使用Iterator遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public void LinkIteratorTraversal() {        Iterator<Integer> i = link.iterator();        int t;        start = System.currentTimeMillis();        while (i.hasNext()) {            t = i.next();        }        end = System.currentTimeMillis();        System.out.println("LinkedList使用Iterator遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public void ArrayListIteratorTraversal() {        ListIterator<Integer> i = array.listIterator();        int t;        start = System.currentTimeMillis();        while (i.hasNext()) {            t = i.next();        }        end = System.currentTimeMillis();        System.out.println("ArrayList使用ListIterator遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public void LinkListIteratorTraversal() {        ListIterator<Integer> i = link.listIterator();        int t;        start = System.currentTimeMillis();        while (i.hasNext()) {            t = i.next();        }        end = System.currentTimeMillis();        System.out.println("LinkedList使用ListIterator遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public void ArrayCycleTraversal() {        int t;        start = System.currentTimeMillis();        for (int i = 0; i < size; i++)            t = array.get(i);        end = System.currentTimeMillis();        System.out.println("ArrayList使用循环遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public void LinkCycleTraversal() {        int t;        start = System.currentTimeMillis();        for (int i = 0; i < size; i++)            t = link.get(i);        end = System.currentTimeMillis();        System.out.println("LinkedList使用循环遍历" + size + "个数据花了"                + (end - start) + "ms");    }    public static void main(String[] args) {        ListTest t = new ListTest(10000000);        System.out.println("*******开始*******");        t.ArrayIteratorTraversal();        t.LinkIteratorTraversal();        t.ArrayListIteratorTraversal();        t.LinkListIteratorTraversal();        t.ArrayCycleTraversal();//      t.LinkCycleTraversal();        System.out.println("*******结束*******");    }}

其中分别用了Iterator,ListIterator和循环来访问ArrayList和LinkedList,输出结果为
//输出结果
*开始*
ArrayList使用Iterator遍历100000个数据花了9ms
LinkedList使用Iterator遍历100000个数据花了9ms
ArrayList使用ListIterator遍历100000个数据花了8ms
LinkedList使用ListIterator遍历100000个数据花了9ms
ArrayList使用循环遍历100000个数据花了6ms
LinkedList使用循环遍历100000个数据花了8049ms
*结束*
//输出结果

使用迭代器时,ArrayList比LinkedList要快上不少,多次测试大约是三倍的样子,Iterator和ListIterator遍历同样的容器效率差不多。但是通过循环遍历,LinkedList跟ArrayList差了三个数量级,其实用循环遍历,看似是顺序遍历List,但是,通过List.get()的方式过的元素值,其实是通过随机访问来实现的,所以也从这个例子可以证明上面所说LinkedList的随机访问效率是非常差的。
由于数据再增长,最后一项LinkedList循环遍历的测试等待时间非常长,所以把这一项测试去掉,大家知道这个效率非常差就好了。我们把测试数据规模扩大到10billion:
//输出结果
*开始*
ArrayList使用Iterator遍历10000000个数据花了37ms
LinkedList使用Iterator遍历10000000个数据花了121ms
ArrayList使用ListIterator遍历10000000个数据花了38ms
LinkedList使用ListIterator遍历10000000个数据花了117ms
ArrayList使用循环遍历10000000个数据花了30ms
*结束*
//输出结果
扩大数据规模后发现有一点是跟上一次测试结果相同的,就是ArrayList的效率明显比LinkedList要高一些,而且另外发现一个问题,用循环遍历ArrayList比用迭代器也要快一些。这是为什么呢?
1、 为什么ArrayList循环遍历比迭代器遍历快?

public E next() {checkForComodification();     int i = cursor;     if (i >= size)        throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;        if (i >= elementData.length)            throw new ConcurrentModificationException();        cursor = i + 1;        return (E) elementData[lastRet = i];}

这是ArrayList中一个继承了Iterator的内部类的next()方法,看了源代码就可以发现其实ArrayList的Iterator就是直接访问ArryList中用于存放数据的一个数组,但是多了一些检查工作。而且,除此之外,遍历时每次iterator.hasNext()也需要额外的时间开销,所以这样遍历自然会比较满一些。
2、 为什么用迭代器遍历ArrayList比LinkedList快一些呢?
这个问题从源码角度还真不好说,搜了一些资料,发现大家对这个效率原因答案都不是特别明确,依稀看到有人的解释我觉得还是蛮有道理的,就是ArrayList的数据结构内存分配是连续的,而LinkedList是不连续的,访问下一个元素的时候需要通过地址去查找,所以有了额外的时间开销。

操作Arrays.asList()

有这么一使用Arrays.asList的例子:

List<Integer>list=Arrays.asList(5,6,8,2,45,1,2,8,5,125,6,8,5);        list.add(5);

通过Arrays.asList方法把一串数字转化成list,然后调用list中的add方法添加元素。但是运行会发现,编译器抛出了一个java.lang.UnsupportedOperationException异常,问题就在add方法,其实除了add方法,remove,retain等对list元素个数会产生变化的方法都是会报这个异常的,究其原因,看一下Arrays.asList的源码
乍一看,发现Arrays.asList返回的就是一个ArrayList对象,但是仔细看发现这个ArrayList并非java.util.ArrayList,而是Arrays的一个内部类,而且没有实现刚才说的add那些方法,而是直接继承了AbstractList,然后AbstractList并没有实现add等方法,只是直接抛出了UnsupportedOperationException异常,所以才会有上述代码发生的那些情况。

正确的使用:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(5, 6, 8, 2,                45, 1, 2, 8, 5, 125, 6, 8, 5));        list.add(5);

总结

对于LinekdList和ArrayList的差异,主要就差别在随机访问与修改,所以建议如下:
1、 如果需要大量删除与添加元素,使用LinkedList
2、 如果需要大量随机访问元素,使用ArrayList
3、 如果需要经常遍历,两者均可,ArrayList效率略高,但是如果选用LinedList,在遍历时尽量使用迭代器,否则相当于随机存取,效率非常低。

0 0
原创粉丝点击