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等。先总结在这,后续再补充。