ArrayList和LinkedList,Vector的区别

本文详细对比了ArrayList和LinkedList在Java中的实现原理,包括数据结构、增删改查的性能分析,以及Vector的线程安全性特点。

开篇吐槽

每次面试,当面试官问到ArrayLIst和LinkedList的区别时,回答的最多的是前者是线程不安全的,而后者是线程安全的等一些很表面的东西。感觉这些东西现在都是家常便饭,现在通过自己的理解来做一个分享。

ArrayList

ArrayList是一个集合,它维护着一个默认的初始变量,一个空的数组实例,还有一个不参与序列化的空的数组实例。

/**
 * Default initial capacity.
 * 默认初始容量。
 */
private static final int DEFAULT_CAPACITY = 10;
/**
     * Shared empty array instance used for empty instances.
     * 用于空实例的共享空数组实例。
     */
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
     * 共享空数组实例,用于默认大小的空实例。我们
     * 将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少
     * 添加第一个元素。
     */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
*这个属性是用来缓冲数据的,ArrayList的长度是这个数组缓冲区的长度,可以使用这个
elementData=EMPTY_ELEMENTDATA来清空缓冲区,再添加第一个元素时容量扩展为DEFAULT_CAPACITY
*/
transient Object[] elementData; // non-private to simplify nested class access
private int size; //ArrayList的长度

ArrayList的构造 方法,小编在这里只列出两种:

/*
这是带参数的
*/
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);
    }
}
//这种是不带参数的
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

不知大家发现没有,ArrayList的构造都和elementData属性值有关系,这就是文章开篇说的这个数据是用来缓冲ArrayList的数据,ArrayList和这个属性值息息相关。

ArrayList中的get

废话不多说,这里直接贴上代码:

public E get(int index) {
    rangeCheck(index);//判断这个下标是否大于list的长度
    return elementData(index);//获取list中维护的数组的那个下标的值
}
private void rangeCheck(int index) {
    if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

这个get方法相当的简单,这里不再做过多的描述。

ArrayList中的get

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

set方法就是对数组中的值进行替换,在替换完毕之后返回被替换掉的值

ArrayList中的add

//数组的末尾添加
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//通过max选出比较大的值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
}
public static int max(int a, int b) {
        return (a >= b) ? a : b;
}
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//表示集合被修改
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//扩容方法
}
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);
}

这一段是add对象,只有一个参数的方法,默认往数组的末尾添加数据,这个也是比较简单的。

public void add(int index, E element) {
    rangeCheckForAdd(index);//判断添加的位置是否会发生下标越界

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //进行数组的复制
    System.arraycopy(elementData, index, elementData, index + 1,
            size - index);
    elementData[index] = element;
    size++;
}

ArrayList中的remove

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    //把多出的那个位置设置为null
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

ArrayList的ListItr和SubList

ArrayList可以通过这些方法返回listIterator和subList对象:

public ListIterator<E> listIterator() {
    return new ListItr(0);
}
public Iterator<E> iterator() {
        return new Itr();
}
public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);//判断取得取得下标是否有异常
        return new SubList(this, 0, fromIndex, toIndex);
}

在上面的代码中可以看到返回一个Itr()方法,下面就看看这个构造方法返回的是什么吧。

Itr

Itr是ArrayList中的一个内部类,它实现了Iterator接口。

int cursor;       // 要返回的下一个元素的索引
int lastRet = -1; // 返回的最后一个元素的索引;-1如果没有
int expectedModCount = modCount;//这个属性modCount主要是记录被修改的次数,并且在迭代中remove数据会抛出快速排序异常,这也就是说阿里巴巴代码规范中的不允许在遍历集合或数组是,进行元素的移除

SubList

SubList是ArrayList中的一个内部类,它实现了RandomAccess接口,说明这个类支持快速随机访问。

private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
//构造方法
SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
}

通过这个内部类的源码可以看出他获取的都是ArrayList的数据,修改的也是ArrayList的数据,所以这也就可以理解在阿里巴巴代码规范中SubList的结果不可以强转成ArrayList,否则会报ClassCastException异常,这是因为SubList返回的是ArrayList的内部类。

LinkedList

LinkedList是一个由链表组成的集合,继承于AbstractSequentialList(这个类继承于AbstractList),实现了List,Deque,Cloneable和Serializable接口。实现了Deque接口,说明LinkedList可以有操作队列的方法,继承了AbstractSequentialList表示LinkedList是一个链表集合。

LinkedList维护着三个属性,我们要了解LinkedList必须要了解它的节点:

transient int size = 0;//表示这个属性不参与序列化
transient Node<E> first;//头节点
transient Node<E> last;//尾节点
//节点结构
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;
        }
}

LinkedList获取第一个和最后一个元素时非常快速的,因为直接由获取的方法,下面贴出获取方法的源码:

//获取头节点
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;//节点中的对象
}
//获取尾节点
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
}

都说LinkedList添加元素较快,下面贴出LinkedList添加元素到指定位置的源码:

public void add(int index, E element) {
        checkPositionIndex(index);//判断插入的下标值是否在LinkedList的size中

        if (index == size)
            linkLast(element);//插入到尾部
        else
            linkBefore(element, node(index));//在非空节点之前插入元素
}
void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
}

从上面的代码中可以很明显的看出LinkedList插入指定位置元素,是对每个节点指向的前一个和后一个元素的属性值做出了修改,不会像ArrayList在插入元素时需要复制元素。

下面的remove方法也是这样的:

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
}
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
}

LinkedList也可以通过方法获取一个ListItr类,这个类跟ArrayList也继承于ListIterator接口,下面是ListItr的属性结构:

private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;//最后返回的节点
        private Node<E> next;//最后返回的节点的下一个节点
        private int nextIndex;
        private int expectedModCount = modCount;
        ....
}

Vector

Vector和ArrayList继承的类和实现的接口是一样的,方法大概也是一样的,Vector比ArrayList少了一个SubList类。Vector是线程安全的,它所有的方法都是加着synchronized的,避免了并发的可能性。

总结

ArrayList和LinkedList相比,ArrayList的get和set方法要比LinkedList快很多,不过是ArrayList是从缓冲数据的数组中获取和设置数据,LinkedList则是从Node数组中获取和设置数据。他们两个如果在尾部添加数据ArrayList要比LinkedList要快,但是指定位置添加的话LinkedList比ArrayList要快。循环获取每个集合中的数据时,ArrayList要比LinkedList快,因为ArrayList实现了快速随机访问接口,而LinkedList只能一个一个的获取,效率较慢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值