ArrayList 和 LinkedList 的底层实现与比较

常见面试题:

ArrayList和LinkedList的区别

  • ArrayList是实现了基于动态数组的数据结构, LinkedList基于链表的数据结构

  • 对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处
    (举例:因为ArrayList的底层是动态数组,它属于一个对象,而LinkedList是链表,它是跟很多对象有关联的,所以, 你查询的话,数组就很快了,相对Linked而言,它联系着很多的对象,查询的时候,你要把他们都查出来,这个时候从性能和时间上,linkedList就不如ArrayList!)

  • 对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
    (ArrayList在添加和删除的时候,底层是创建一个新的数组,而LinkedList却只要修改一下指针就ok了)

ArrayList的底层实现

public class ArrayList<E> extends AbstractList<E>

ArrayList的父类是AbstractList类,具体父类、接口的关系,我在HashMap详解中有集合类的关系图。

首先,我们来看一下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

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

ArrayList的数据结构

ArrayList包含两个重要的属性:elementData和size。

  • size

ArrayList包含的元素的个数。

  • elementData

(1)Object数组(也就是常说的ArrayList的底层是数组),所以ArrayList是不能放基本数据的(这是和数组的一大区别)。

(2)使用transient修饰。如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。但在实际的传输过程中,我们显然是可以获取到数组中的元素的,那么这是如何实现的呢?又为什么要要用transient修饰呢?我们先看看ArrayList的构造函数已经扩容原理。

ArrayList的容量

ArrayList有两个构造函数,public ArrayList()和public ArrayList(int initialCapacity)。

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

如上述的源码所示:

ArrayList()的默认capacity为10,ArrayList(int initialCapacity)的capacity为initialCapacity。

PS:size是数组中的存储的元素的个数,而capacity则是数组的容量。

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;
    }

ArrayList添加元素的时候,先要进行ensureCapacityInternal(size + 1)操作:即判断capacity与size+1的大小,当capacity<size+1时,就会进行resize扩容,扩容时是创建一个容量为newCapacity = oldCapacity + (oldCapacity >> 1),即扩大为原来的1.5倍的数组,然后将原来的元素给复制到新数组,代码如下:

    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);
    }
elementData为什么用transient修饰

前面就留下这个疑问了,现在终于可以解答了。

在上面我们知道,capacity是大于等于size的,并且绝大多数时候是大于size的。所以如果直接序列化elementData数组,那么就会浪费(capacity-size)个元素的空间,特别是当capacity-size非常大的时候,这种浪费是非常不合算的。那么elementData不被序列号,传输时又是怎么读写的呢?ArrayList实现了writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectInputStream s)方法。这两个方法的作用具体见什么是writeObject 和readObject?可定制的序列化过程

/**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

ArrayList的remove操作

        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

如上面的源码所示,ArrayList的源码,只会改变size的值,但并不会自动改变capacity的值,但ArrayList提供了trimToSize()让我们自己控制。

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

ArrayList的其他方法就懒得讲了。

 

LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

它的父类关系具体还是见我在HashMap详解中有集合类的关系图。

 

LinkedList的数据结构

    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;

如上面源码所示,LinkedList的底层实现是一个双向链表。

LinkedList的Add操作

LinkedList提供了三个添加元素的方法:

public void addFirst(E e);

public void addLast(E e);

public boolean add(E e);
其中,add方法是在链表尾部添加元素:
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

LinkedList按下标获取元素

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

其他的也没什么特殊的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值