前言
ArrayList是基于动态数组的数据结构,LinkedList是基于链表的数据结构。
下面来分析它们的源码,比较一下两者之间的不同。以下分析基于Oracle JDK1.8。
ArrayList源码分析
构造方法
无参构造:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
初始化了一个大小为0的Object类型的数组。
有参构造:
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
public ArrayList(int var1) {
if(var1 > 0) {
this.elementData = new Object[var1];
} else {
if(var1 != 0) {
throw new IllegalArgumentException("Illegal Capacity: " + var1);
}
this.elementData = EMPTY_ELEMENTDATA;
}
}
初始化了一个大小指定的Object类型的数组。
由此可见,确实,ArrayList维护了一个数组。
add(int var1, E var2)
public void add(int var1, E var2) {
this.rangeCheckForAdd(var1);
this.ensureCapacityInternal(this.size + 1);
System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1);
this.elementData[var1] = var2;
++this.size;
}
第一步this.rangeCheckForAdd(var1)
是去检查索引的正确性:
private void rangeCheckForAdd(int var1) {
if(var1 > this.size || var1 < 0) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
超出正常的范围,就抛出索引越界异常。
第二步this.ensureCapacityInternal(this.size + 1)
是去检查数组的容量:
private void ensureCapacityInternal(int var1) {
if(this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
var1 = Math.max(10, var1);
}
this.ensureExplicitCapacity(var1);
}
从ensureCapacityInternal()中可以看出,数组默认的最小容量是10。
然后进入ensureExplicitCapacity(),检查数组是否需要扩容:
private void ensureExplicitCapacity(int var1) {
++this.modCount;
if(var1 - this.elementData.length > 0) {
this.grow(var1);
}
}
如果要现在要插入的位置不在数组大小的范围内,就需要扩容,进入grow():
private void grow(int var1) {
int var2 = this.elementData.length;
int var3 = var2 + (var2 >> 1);
if(var3 - var1 < 0) {
var3 = var1;
}
if(var3 - 2147483639 > 0) {
var3 = hugeCapacity(var1);
}
this.elementData = Arrays.copyOf(this.elementData, var3);
}
正常情况下,通过int var3 = var2 + (var2 >> 1)
这一步,定义了新数组的大小为原来的1.5倍,最后this.elementData = Arrays.copyOf(this.elementData, var3)
把原来的数组拷贝到新的数组。
回到add(int var1, E var2),
第三步System.arraycopy(this.elementData, var1, this.elementData, var1 + 1, this.size - var1)
做的操作是其实也是拷贝数组,只不过是把元素拷贝给自己,实际上就是将插入点之后(也包括插入点)的元素往后移动一位。
后面两步操作就很容易懂了,就是将插入点原来的元素替换为现在要插入的元素,并且将集合的大小+1。
这里就介绍一种方法add(int var1, E var2),其他add()重载的方法或者remove()重载的方法都是类似的。
总的来说就是ArrayList内部维护了一个动态的数组,我们增删改查都是对这个数组进行操作。
LinkedList源码分析
构造方法
transient int size;
public LinkedList() {
this.size = 0;
}
这个无参构造很简单。
add(int var1, E var2)
public void add(int var1, E var2) {
this.checkPositionIndex(var1);
if(var1 == this.size) {
this.linkLast(var2);
} else {
this.linkBefore(var2, this.node(var1));
}
}
第一步this.checkPositionIndex(var1)
很简单,就是去检查索引的合法性,不合法就抛出索引越界异常:
private void checkPositionIndex(int var1) {
if(!this.isPositionIndex(var1)) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
private boolean isPositionIndex(int var1) {
return var1 >= 0 && var1 <= this.size;
}
第二步,如果插入点是集合的末尾,进入linkLast(E var1):
void linkLast(E var1) {
LinkedList.Node var2 = this.last;
LinkedList.Node var3 = new LinkedList.Node(var2, var1, (LinkedList.Node)null);
this.last = var3;
if(var2 == null) {
this.first = var3;
} else {
var2.next = var3;
}
++this.size;
++this.modCount;
}
这里涉及到了LinkedList的静态内部类Node:
private static class Node<E> {
E item;
LinkedList.Node<E> next;
LinkedList.Node<E> prev;
Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
this.item = var2;
this.next = var3;
this.prev = var1;
}
}
Node表示链表的结点,item表示当前节点处的元素,next指向了下一个结点,prev指向了上一个结点。由此可见,LinkedList是基于双向链表的数据结构。
那么我们再看linkLast(E var1)就一目了然了,就是在链表尾部增加了一个新的结点罢了。
如果插入点不是在尾部,就进入linkBefore(E var1, LinkedList.Node var2):
void linkBefore(E var1, LinkedList.Node<E> var2) {
LinkedList.Node var3 = var2.prev;
LinkedList.Node var4 = new LinkedList.Node(var3, var1, var2);
var2.prev = var4;
if(var3 == null) {
this.first = var4;
} else {
var3.next = var4;
}
++this.size;
++this.modCount;
}
做的操作也显而易见,在链表当中插入了一个新的结点。
至于其他add(),remove()等等操作也是类似的。
总的来说就是LinkedList内部维护了一个双向链表,我们增删改查都是对这个链表进行操作。
ArrayList和LinkedList的比较
1.ArrayList基于数组,LinkedList基于链表。
2.对于随机访问get()和set(),ArrayList优于LinkedList,因为LinkedList要移动指针。
3.对于add()和remove(),LinkedList比较占优势,因为ArrayList要移动数据。
4.其他操作indexOf(),lastIndexOf(),contains等(),两者差不多。
以上只是理论上分析,事实上也不一定,比如ArrayList在末尾插入和删除数据就不涉及到数据移动。
而且实际业务场景可能很复杂,孰优孰劣需要综合考虑。