Java集合之ArrayList / Vector / LinkedList

Java集合之ArrayList /Vector / LinkedList

  • ArrayList
  • Vector
  • LinkedList
  • 对比

Java集合中List接口的实现类主要有ArrayList/Vector/LinkedList。本篇博客主要记录三者的常用用法及区别。

ArrayList

ArrayList可以理解为长度可变的数组Array,JVM为其分配连续的内存空间,ArrayList可以进行动态的扩容,默认初始化容量为10(默认空的构造函数new ArrayList()),也可以new ArrayList(int initialCapacity)自定义初始化容量。

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

ArrayList方法

boolean add()

添加一个元素到list尾部,其首先会进行扩容校验,扩容检验过程中会通过if (minCapacity - elementData.length > 0) grow(minCapacity);判断当前list是否能够拥有足够的容量容纳新添加的元素,如果不能容量则执行grow()方法进行扩容,可见,新的容量增加原容量的一半,并且每进行一次扩容就会有一次数据的拷贝。

/**
 * 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;  //然后将元素e添加到list最后位置
    return true;
}
/**
 * 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);
}

void add(int index, E element)

添加一个元素到list指定的位置,同样,首先进行扩容校验,然后复制移动数据。从添加元素的两个方法可以看出,ArrayList的数据扩容和指定位置添加数据都会进行数组的复制,比较耗时,因此在使用过程中,尽可能避免扩容操作,也就是初始化ArrayList尽可能指定准确的大小。

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);//复制数据,并将index处的元素后移
    elementData[index] = element;
    size++;
}

E get(int index)

返回list指定位置的数据元素,ArrayList本身就是一个数组,数据都是保存在一个Object[] elementData数组的buffer中,因此只需要直接返回elementData[index]即可,可见,ArrayList的查找还是比较高效的(相对LinkedList而言)。

/**
 * 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);
}

Vector

Vector是List接口的一个多线程安全实现类,其结构与ArrayList非常相似,同样是一个线性的动态可扩容数组,不过其扩容机制与ArrayList有一定的差别。

Vector方法

boolean add(E e) / void addElement(E obj)

两个方法都是向Vector尾部添加一个元素,先进行扩容校验,然后把元素添加到末尾,从grow()函数可以看出,此时多了一个变量capacityIncrement(扩容量),这个扩容量可以通过构造函数 public Vector(int initialCapacity, int capacityIncrement)进行初始化,如果有初始化的扩容量,则每次扩容增加一个单位的扩容量,否则直接扩容至原数据容量的2倍。

/**
 * 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;
}
/**
 * Adds the specified component to the end of this vector,
 * increasing its size by one. The capacity of this vector is
 * increased if its size becomes greater than its capacity.
 *
 * <p>This method is identical in functionality to the
 * {@link #add(Object) add(E)}
 * method (which is part of the {@link List} interface).
 *
 * @param   obj   the component to be added
 */
public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1); //扩容校验
    elementData[elementCount++] = obj; //赋值至末尾
}
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

E get(int index) / void add(int index, E element)

基本与ArrayList保持一致

/**
 * Returns the element at the specified position in this Vector.
 *
 * @param index index of the element to return
 * @return object at the specified index
 * @throws ArrayIndexOutOfBoundsException if the index is out of range
 *            ({@code index < 0 || index >= size()})
 * @since 1.2
 */
public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    return elementData(index);
}

Vector相关的方法基本都添加了synchronized关键字,从而保证了多线程下的线程安全性,但是在单线程下,Vector却与ArrayList的性能有一定的差距。

LinkedList

从图中可以看出,LinkedList同样实现了List接口,因此其实现了List接口里面的所有方法。实际上,LinkedList是基于双向链表来实现的,因此其拥有链表具有的增加和删除元素的效率高的优点,这一点基本上和顺序表为结构基础的ArrayList和Vector集合相对立。

LinkedList方法

boolean add(E e) / void add(int index, E element)

首先看下LinkedList源码部分定义的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;
    }
}
/**
 * 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); //将元素e链接到链表末尾
    return true;
}
/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last; //临时保存末尾节点
    final Node<E> newNode = new Node<>(l, e, null); //构造元素值为e的新节点
    last = newNode; //新节点为尾节点
    if (l == null)
        first = newNode;
    else
        l.next = newNode; //.next连接
    size++; //容量+1
    modCount++; //修改数标志+1
}
/**
 * Inserts the specified element at the specified position in this list.
 * Shifts the element currently at that position (if any) and any
 * subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    checkPositionIndex(index);
    if (index == size)
        linkLast(element); //判断如果index末尾位置,则直接将元素e链接到链表末尾
    else
        linkBefore(element, node(index)); //否则取出索引处节点(node(index)),然后将索引处的pred.next指向新节点
}
/**
 * Inserts element e before non-null Node succ.
 */
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);  //初始化元素为e的新节点
    succ.prev = newNode; //将索引节点与新节点链接
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode; //将临时保存的前向节点链接到新节点
    size++;
    modCount++;
}

整体过程可以大致表示如下,其实就是双向链表的插入过程。

此外LinkedList还实现了Deque接口,也实现了Deque里addFirst(E e) / addLast(E e) / offerFirst(E e) / offerLast(E e) / removeFirst() / removeLast() / pollFirst() / pollLast() / peekFirst() / peekLast()等方法。

对比

三者都实现了List接口,是数据结构中线性表结构的Java实现方式,ArrayList和Vector是顺序表结构,其在内存中地址分配是连续的,而LinkedList是链式存储结构,其在内存分配的地址是不连续的。ArraList和Vector相比,ArrayList是Vector的替代版本,现在Vector都很少使用了,Vector因为是添加了同步关键字而具有线程安全的特点,但是sychronized的性能非常的差,使用List接口下的线程安全类更多的会转向Queue或java.util.concurrent.ConcurrentLinkedQueue等。先总结在这,后续再补充。

猜你喜欢

转载自blog.csdn.net/xautjerry/article/details/80340553