[java] ArrayList和LinkedList实现原理及比较

Table of Contents

ArrayList

Add 方法

Get方法

remove方法

Vector

ArrayList在循环过程中删除,会不会出问题,为什么?

LinkedList

LinkedList的get方法实现原理

LinkedList的size方法实现原理

LinkedList的add方法实现原理

LinkedList的remove方法实现原理

问题:ArrayList和LinkedList有什么区别,它们是怎么扩容的?

问题:Arrays.sort的排序方法是什么?

问题:Array和ArrayList有何区别?什么时候更适合用Array?


本篇分析基于jdk1.8。

数组与链表的基本知识在这里不作展开,属于数据结构与算法的重点内容。

ArrayList

ArrayList是一个容器,容器是用来存储对象的,ArrayList当中存储对象的变量是elementData,elementData的定义如下:

 /**
     * 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

非常简单,就是一个对象数组而已。注意,它使用了transient关键字来修饰,这意味着ArrayList不会被序列化。

直接上几个ArrayList里的方法。

Add 方法

/**
     * 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++自然就是当前数组的后一位。

其他值得注意的是,此方法并没有做任何同步处理。并且,事实上整个Arraylist里的所有方法都没有做任何同步处理。因此这些涉及数据操作的方法都不是线程安全的。

Get方法

 /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

做了两件事情

  • 检测传入的参数,即数组的下标,是否越界
  • 若通过检测,则返回对应位置的元素即可,因为数组是有序排列的。

remove方法

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

这个实现很有意思的是它判断了一下要删除的对象是否是null,为什么要做这个判断呢,因为他们判断对象是否相等的方法不一样。null直接用两个等号判断,而非null对象,则是用equals方法判断相等。

遍历数组,找到相等对象,确定下标,然后删除当前数组中该下标的元素。删除方法为fastRemove,实现如下

/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

由于是删除,所以不需要做越界检查。结构变化数modCount加一,计算需要移动的数据元素,为当前数组大小,减去删除元素位置,再减去1,为什么要减去1因为数组下标从0开始,很好理解,做个草图如下:

当前数组元素一共9个,然后假设我要删除的是第6个元素。即size=9,index=5(从0开始)。要删除的元素是6,需要移动的元素即为7,4,8一共三个。

size-index-1 = 9 - 5 -1 = 3.

计算正确,一个测试用例诞生~。

然后将五个参数传入了System类下的arraycopy方法。这个方法的作用就是根据需要移动的元素,重新构造一个没有要删除元素的新数组,也即实现了删除的功能。这里额外插一句,从clean code的角度来讲,这个方法的参数已经太多了,clean code的建议是三个最多,只能说凡事无绝对吧。或者也可以说jdk的代码也并非从各个角度来看都完美无缺。

方法arraycopy在System类看不到实现,它是一个native方法。java众多的底层方法,实现几乎都是native方法,也就是用别的语言实现的。作为一个现代的程序员,或者说作为一个流水线上的程序员,大多数人真的一辈子也许也不需要知道这个方法到底如何实现的。

Vector

那么,如果要使用线程安全的容器,应该使用哪一个呢。答案就是Vector。

Vector的add方法如下:

/**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

用了synchronized直接修饰这个方法。除此之外实现逻辑和ArrayList几乎一样。modCount从注释来看这个变量代表的是这个容器的结构变化次数,所谓结构变化很好理解,就是数组中元素增加了一个,减少了一个,就是结构的变化。这里把modCount放在这里做计算,是一个和ArrayList不一样的地方,ArrayList是在check的时候来做。但这仅仅是代码风格的不统一。</

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值