ArrayList和LinkedList,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只能一个一个的获取,效率较慢。