Java中关于ArrayList以及LinkedList

一、疑问产生

ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组

LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
 

乍一看好像没啥问题,那么,写代码测试咯

1,000次的add、get、remove  ArrayList 与 LinkedList 对比

以下为三次输出,数字代表耗费时间,单位为纳秒

Test times = 1,000
----------------------------------------------------------------------------------------------------
1153728 <--ArrayList add    1170948 <--ArrayList add    1213081 <--ArrayList add
681832 <--LinkedList add    655819 <--LinkedList add    673039 <--LinkedList add
----------------------------------------------------------------------------------------------------
161940 <--ArrayList get        161939 <--ArrayList get        163406 <--ArrayList get
6065776 <--LinkedList get    5955129 <--LinkedList get    5856573 <--LinkedList get
----------------------------------------------------------------------------------------------------
248772 <--ArrayList remove    257564 <--ArrayList remove    253901 <--ArrayList remove
382500 <--LinkedList remove    380668 <--LinkedList remove    388728 <--LinkedList remove

嗯……基本没问题,LinkedListadd方面略有优势,但是在getremove方面性能差较多,特别是get方法耗时是ArrayList的40倍左右

修改方法次数为10,000,再试

Test times = 10,000
----------------------------------------------------------------------------------------------------
5855107 <--ArrayList add    5932047 <--ArrayList add    6190711 <--ArrayList add
4621874 <--LinkedList add    4612349 <--LinkedList add    4777952 <--LinkedList add
----------------------------------------------------------------------------------------------------
1465884 <--ArrayList get    1553448 <--ArrayList get    1757155 <--ArrayList get
65177042 <--LinkedList get    65069692 <--LinkedList get    64154844 <--LinkedList get
----------------------------------------------------------------------------------------------------
2087263 <--ArrayList remove    2206703 <--ArrayList remove    2081034 <--ArrayList remove
3250883 <--LinkedList remove    3180538 <--LinkedList remove    3334052 <--LinkedList remove

可以看出add方面二者的差距逐渐在缩小,而getremove则是差距越来越明显,隐隐有种预感

修改方法次数为100,000,再试

Test times = 100,000
----------------------------------------------------------------------------------------------------
13268426 <--ArrayList add    12983749 <--ArrayList add    13512068 <--ArrayList add
14465387 <--LinkedList add    15104353 <--LinkedList add    15269589 <--LinkedList add
----------------------------------------------------------------------------------------------------
5324590 <--ArrayList get    5245452 <--ArrayList get    5358297 <--ArrayList get
14461859917 <--LinkedList get    14871016175 <--LinkedList get    15722798537 <--LinkedList get
----------------------------------------------------------------------------------------------------
6943620 <--ArrayList remove    7003340 <--ArrayList remove    6839935 <--ArrayList remove
12048383 <--LinkedList remove    11443857 <--LinkedList remove    11187758 <--LinkedList remove

此时ArrayListadd方法的性能上已经超越了LinkedList,也就是意味着无论是add、get还是remove方法,ArrayList的性能都比LinkedList性能要好

修改方法次数为300,000,再试(本来想修改为100万的,但是LinkedList耗时太长所以放弃)

Test times = 300000
----------------------------------------------------------------------------------------------------
21563985 <--ArrayList add    19376701 <--ArrayList add    20826463 <--ArrayList add
61810016 <--LinkedList add    52228099 <--LinkedList add    57616806 <--LinkedList add
----------------------------------------------------------------------------------------------------
5531595 <--ArrayList get    5593513 <--ArrayList get    5213211 <--ArrayList get
187586464777 <--LinkedList get    184798077022 <--LinkedList get    193717198796 <--LinkedList get
----------------------------------------------------------------------------------------------------
7322823 <--ArrayList remove    7544848 <--ArrayList remove    7362391 <--ArrayList remove
15116077 <--LinkedList remove    14866938 <--LinkedList remove    14829935 <--LinkedList remove
可以看到,ArrayList在性能上已经遥遥领先,LinkedList难望其项背,相信即使数据再扩大,也必然是这个结果

为什么是这样的结果呢?那么,让我们来看看源码一探究竟吧

 

二、分析

首先找到ArrayListadd方法

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

第二行为将元素E赋值给size++的位置(size计数器+1)

第一行则是为私有方法,代码如下:

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

modCount为java.util.AbstractList的成员变量,声明:

    protected transient int modCount = 0;

这个变量作为ArrayList快速失败的机制的实现(面对并发的修改时)

然后进行判断,检查目前容量的长度+1是否大于数组的长度,如果是则对数组进行扩容

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

grow方法对数组进行扩容

int newCapacity = oldCapacity + (oldCapacity >> 1);

数组扩容的这一行,标记了扩充容量为原有容量的大约1.5倍

然后比较数组扩容后的长度以及将要到达的容量的长度,以及Integer的最大值等等,最后调用的是Arrays.copyOf方法,而该方法的底层实现为native的System.arraycopy方法,至此,了解了数组在add时处理的方法

分析其过程,即:

add方法被调用-->判断数组原有长度是否还够-->不够则扩充数组容量为1.5倍-->调用native方法复制数组

tips:ArrayList默认数组长度为10哦

我们再来看看LinkedList

    /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

其offer(E e)方法也是调用的add(E e)方法哦,但offerFirst和offerLast方法调用的分别是linkFirst和linkLast方法

来看看linkLast方法

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

该方法第一行就有一个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;
        }
    }

NodeLinkedList的静态内部类,其定义了三个属性,E为元素本身,next为后一元素,prev为前一元素

然后回过头来看linkLast方法

第一行将最后一个元素last定义为l

第二行创建新的内部类对象,该对象的前一个为l,本身为参数e,后一个为null(对象创建

第三行将创建的对象赋值给last

接下来判断l是否为null(即原来的链表是否为空链表)然后进行赋值操作,计数器+1

linkFirst方法与linkLast方法类似,在此不做赘述

分析其过程,即:

add方法被调用-->创建引用赋值-->创建内部类对象-->改变引用赋值

 

三、结果

二者对比其add方法,不难发现

当数组较小时,因为ArrayList在添加元素时,需要频繁的去扩容且复制数组,因此时间开销很大,而LinkedList只是创建了一个对象,时间开销基本固定,因此二者的速度为LinkedList速度较快

但是当数组越来越大,ArrayList不需要那么频繁的扩容时(例如十万级别的容量,扩容一次容量后容量则达到了十五万,此后添加的五万条数据都是瞬间加入基本无耗时的开销),时间开销相对减小,但LinkedList则还是需要一个个的创建对象(十万到十五万,需要创建五万个对象),需要频繁的去申请内存然后在堆中创建对象,这样的时间消耗是巨大的(还不排除当内存不够时,还得启动GC回收内存后才能创建)

所以,ArrayList的效率会在达到一定的数量级别后要比LinkedListadd方法效率要高,至于这个级别是多少,是根据具体的环境、条件等等很多因素都会影响,毕竟ArrayList调用的是native方法,依赖于编译器的实现。

在继续测试了10,100两种次数后,发现remove方法也是在100次这个级别,ArrayList才超越了LinkedList,此前是linkedList占据优势,至于get方法,这个则没啥好比的,毕竟循环遍历链表怎么可能会比数组的下标更快对吧哈哈哈哈

 

四、反思

从上面的测试结果看来,即使ArrayList在做万级别数据的操作时,表现都是十毫秒级别的,这个时间基本可以忽略(一些特殊的应用除外),那么我们是否在大部分情况下,都可以使用ArrayList来存储对象操作对象呢?

这个就由各位看官自行把握吧,毕竟,LinkedList所具有的offerFirst、push等等方法,ArrayList都是不具有的哦,怎样选择,应该看具体需求吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值