ArrayList与LinkedList比较

ArrayList与LinkedList比较

1.实现方式

ArrayList内部结构为数组,定义如下:

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class access

LinkedList内部结构为双向循环链表,定义如下:

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

// Node节点定义
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {   
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

2.使用场景

ArrayList适用于随机访问
LinkedList适用于于随机位置增加、删除

3.插入删除

ArrayList在插入删除时需要移动index后面的所有元素
LinkedList在插入删除时只需遍历,不需要移动元素

4.随机访问

ArrayList支持通过下标访问元素,效率高
LinkedList每次访问通过头尾遍历,效率低

5.空间占用

ArrayList因为有扩容操作,在尾部预留有额外空间,每次扩容为150%,造成一定的空间浪费(初始大小为10)
LinkedList虽然没有浪费空间,但是每个元素都存储在Node对象中,占用空间比ArrayList大

6.遍历方式

ArrayList可以使用for循环,forEach,iterator
LinkedList一般使用forEach,iterator

7.继承接口

ArrayList继承了RandomAccess接口,RandomAccess是一个标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的List中性能更加高效(在Collections二分查找时)。如果集合类实现了RandomAccess,则尽量用for循环来遍历,没有实现则用Iterator进行遍历。
LinkedList继承了Deque接口,便于实现栈和队列

8.性能分析

操作 ArrayList LinkedList
get(index) O(1) O(n)
add() O(1) O(1)
add(index) O(n) O(n)
remove() O(n) O(n)

可以发现,LinkedList的add(index),和remove()的复杂度也是O(n),与ArrayList并没有差别,这是因为在增删之前需要先得到增删元素的位置,然后才能进行增删,然而LinkedList只能通过遍历来得到位置,因此复杂度为O(n),并不是O(1)。

  1. 末端插入,虽然二者都是O(1),但是LinkedList每次插入都要new一个对象。因此,当数据量小时,LinkedList速度快,随着数据量的增加,ArrayList速度更快。
  2. 随机插入
    LinkedList对于插入有一个优化:当插入位置小于(size/2)时从头遍历,当插入位置大于(size/2)时,从尾遍历。
    2.1 在前半段随机插入,一般来说,此时的LinkedList效率高于ArrayList。
    2.2 在后半段随机插入,此时很难判断,因为在后半段,ArrayList的copy()消耗减少,而对于LinkedList来说效率不变,因此二者的性能相差不大。

代码验证,使用一个大小为1000000的List,向其中插入500000条数据,验证在不同插入位置List的性能

耗时 ArrayList LinkedList
插入位置:末尾 0.034s 0.034s
插入位置:999999 17.166s 447.135s
插入位置:500001 120.862s 1024.761s
插入位置:1 307.608s 0.045s
插入位置:0 381.185s 0.039s
插入位置:250000 240.943s 621.257s
插入位置:0,2,4,6... 63.837s 692.319s

总结:只有当频繁在List前端位置进行增删操作,才选用LinkedList。一般情况,都选用ArrayList。
测试代码:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Test
 */
public class Test {

    //在末端插入
    public static void addTest(List list, int num) {
        long startTime = System.currentTimeMillis();
        for(int i=0; i<num; i++) {
            int a = (int) Math.round(Math.random()*num);
            list.add(a);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("addTime: " + (endTime-startTime)/1000.0 + "s  size:" + list.size());
    }

    // 在指定位置插入
    public static void insertTest(List list, int num, int index) {

        long totalTime=0;
        long startTime=0;
        long endTime=0;

        startTime = System.currentTimeMillis();
        for(int i=0; i<num; i++) {
            int a = (int) Math.round(Math.random()*num);

            startTime = System.currentTimeMillis();
            list.add(index, a);
            endTime = System.currentTimeMillis();
            totalTime += (endTime-startTime);
        }
        System.out.println("insertTime: " + (totalTime)/1000.0 + "s  size:" + list.size());
    }

    // 间隔插入
    public static void insertTest(List list, int num) {

        long totalTime=0;
        long startTime=0;
        long endTime=0;

        for(int i=0; i<num; i++) {
            int a = (int) Math.round(Math.random()*num);

            startTime = System.currentTimeMillis();
            list.add(2*i, a);
            endTime = System.currentTimeMillis();
            list.remove(2*i);
            totalTime += (endTime-startTime);
        }

        System.out.println("insertTime: " + (totalTime)/1000.0 + "s  size:" + list.size());
    }


    public static void main(String[] args) {
        
        ArrayList<Integer> array = new ArrayList<Integer>();
        LinkedList<Integer> link = new LinkedList<Integer>();
        int num = 1000000;
        int index = 250000;


        //Test.addTest(link, num);
        //Test.addTest(link, 500000);
        //Test.insertTest(link, 500000, index);
        //Test.insertTest(link, 500000);

        Test.addTest(array, num);
        //Test.addTest(array, 500000);
        //Test.insertTest(array, 500000, index);
        Test.insertTest(array, 500000);

    }
}
posted @ 2019-03-12 15:32  bosslv  阅读(410)  评论(0编辑  收藏  举报