ArrayList和LinkedList的比较

前言

ArrayList是基于动态数组的数据结构,LinkedList是基于链表的数据结构。

下面来分析它们的源码,比较一下两者之间的不同。以下分析基于Oracle JDK1.8。

ArrayList源码分析

构造方法

无参构造

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

初始化了一个大小为0的Object类型的数组。

有参构造

    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];

    public ArrayList(int var1) {
        if(var1 > 0) {
            this.elementData = new Object[var1];
        } else {
            if(var1 != 0) {
                throw new IllegalArgumentException("Illegal Capacity: " + var1);
            }

            this.elementData = EMPTY_ELEMENTDATA;
        }

    }

初始化了一个大小指定的Object类型的数组。

由此可见,确实,ArrayList维护了一个数组

add(int var1, E var2)

    public void add(int var1, E var2) {
        this.rangeCheckForAdd(var1);
        this.ensureCapacityInternal(this.size + 1);
        System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
        this.elementData[var1] = var2;
        ++this.size;
    }

第一步this.rangeCheckForAdd(var1)是去检查索引的正确性:

    private void rangeCheckForAdd(int var1) {
        if(var1 > this.size || var1 < 0) {
            throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
        }
    }

超出正常的范围,就抛出索引越界异常

第二步this.ensureCapacityInternal(this.size + 1)是去检查数组的容量:

    private void ensureCapacityInternal(int var1) {
        if(this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            var1 = Math.max(10, var1);
        }

        this.ensureExplicitCapacity(var1);
    }

ensureCapacityInternal()中可以看出,数组默认的最小容量是10。

然后进入ensureExplicitCapacity(),检查数组是否需要扩容

    private void ensureExplicitCapacity(int var1) {
        ++this.modCount;
        if(var1 - this.elementData.length > 0) {
            this.grow(var1);
        }

    }

如果要现在要插入的位置不在数组大小的范围内,就需要扩容,进入grow()

    private void grow(int var1) {
        int var2 = this.elementData.length;
        int var3 = var2 + (var2 >> 1);
        if(var3 - var1 < 0) {
            var3 = var1;
        }

        if(var3 - 2147483639 > 0) {
            var3 = hugeCapacity(var1);
        }

        this.elementData = Arrays.copyOf(this.elementData, var3);
    }

正常情况下,通过int var3 = var2 + (var2 >> 1)这一步,定义了新数组的大小为原来的1.5倍,最后this.elementData = Arrays.copyOf(this.elementData, var3)把原来的数组拷贝到新的数组。

回到add(int var1, E var2)
第三步System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1)做的操作是其实也是拷贝数组,只不过是把元素拷贝给自己,实际上就是将插入点之后(也包括插入点)的元素往后移动一位。

后面两步操作就很容易懂了,就是将插入点原来的元素替换为现在要插入的元素,并且将集合的大小+1。

这里就介绍一种方法add(int var1, E var2),其他add()重载的方法或者remove()重载的方法都是类似的。

总的来说就是ArrayList内部维护了一个动态的数组,我们增删改查都是对这个数组进行操作。

LinkedList源码分析

构造方法

    transient int size;

    public LinkedList() {
        this.size = 0;
    }

这个无参构造很简单。

add(int var1, E var2)

    public void add(int var1, E var2) {
        this.checkPositionIndex(var1);
        if(var1 == this.size) {
            this.linkLast(var2);
        } else {
            this.linkBefore(var2, this.node(var1));
        }

    }

第一步this.checkPositionIndex(var1)很简单,就是去检查索引的合法性,不合法就抛出索引越界异常

    private void checkPositionIndex(int var1) {
        if(!this.isPositionIndex(var1)) {
            throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
        }
    }

    private boolean isPositionIndex(int var1) {
        return var1 >= 0 && var1 <= this.size;
    }

第二步,如果插入点是集合的末尾,进入linkLast(E var1)

    void linkLast(E var1) {
        LinkedList.Node var2 = this.last;
        LinkedList.Node var3 = new LinkedList.Node(var2, var1, (LinkedList.Node)null);
        this.last = var3;
        if(var2 == null) {
            this.first = var3;
        } else {
            var2.next = var3;
        }

        ++this.size;
        ++this.modCount;
    }

这里涉及到了LinkedList的静态内部类Node

    private static class Node<E> {
        E item;
        LinkedList.Node<E> next;
        LinkedList.Node<E> prev;

        Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
            this.item = var2;
            this.next = var3;
            this.prev = var1;
        }
    }

Node表示链表的结点item表示当前节点处的元素,next指向了下一个结点,prev指向了上一个结点。由此可见,LinkedList是基于双向链表的数据结构。

那么我们再看linkLast(E var1)就一目了然了,就是在链表尾部增加了一个新的结点罢了。

如果插入点不是在尾部,就进入linkBefore(E var1, LinkedList.Node var2)

    void linkBefore(E var1, LinkedList.Node<E> var2) {
        LinkedList.Node var3 = var2.prev;
        LinkedList.Node var4 = new LinkedList.Node(var3, var1, var2);
        var2.prev = var4;
        if(var3 == null) {
            this.first = var4;
        } else {
            var3.next = var4;
        }

        ++this.size;
        ++this.modCount;
    }

做的操作也显而易见,在链表当中插入了一个新的结点

至于其他add()remove()等等操作也是类似的。

总的来说就是LinkedList内部维护了一个双向链表,我们增删改查都是对这个链表进行操作。

ArrayList和LinkedList的比较

1.ArrayList基于数组,LinkedList基于链表

2.对于随机访问get()set()ArrayList优于LinkedList,因为LinkedList要移动指针

3.对于add()remove()LinkedList比较占优势,因为ArrayList要移动数据

4.其他操作indexOf(),lastIndexOf(),contains等(),两者差不多。

以上只是理论上分析,事实上也不一定,比如ArrayList在末尾插入和删除数据就不涉及到数据移动。
而且实际业务场景可能很复杂,孰优孰劣需要综合考虑。

参考:ArrayList和LinkedList的用法区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值