ArrayList与LinkedList的区别常常被人提及,清楚了解二者的区别有利于夯实自己的开发基本功,在合适的场合选择合适的数据结构,能够帮助自己写出更加优质的代码,本文结合二者的源码,对它们进行组成分析和区别分析。
ArrayList基于动态数据(顺序表)进行实现,且默认存储的容量为10
LinkedList基于双向链表的数据结构进行实现,具有头尾指针,单个结点具有prev指针和next指针
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
modCount++;
add(e, elementData, size);
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
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
private Object[] grow() {
return grow(size + 1);
}
众所周知,一个数组如果往里面添加值超过了边界,那么便要对这个数组进行扩容;那么我们假设有一个ArrayList,现在里面的元素已经满了,我们再试图往里面添加元素,这时候便要调用add()函数了,那么依照我上文添加的源码,add函数便会去调用grow()函数,那么我们便对grow函数进行一番分析。
那么我们看到,当调用add函数的时候,可能存在两种情况:
1.原有列表不存在元素(未初始化),直接生成一个新的顺序表,大小取默认大小和添加元素大小的最大值
2.原有列表存在元素,这时候需要完成两项工作
第二步无非就是O(n)级的复制,这里我们重点关注扩容那一步发生了什么,也就是调用ArraysSupport.newLength()那一步发生了什么,来看看源码;
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max(minGrowth, prefGrowth) + oldLength;
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
}
private static int hugeLength(int oldLength, int minGrowth) {
int minLength = oldLength + minGrowth;
if (minLength < 0) { // overflow
throw new OutOfMemoryError("Required array length too large");
}
if (minLength <= MAX_ARRAY_LENGTH) {
return MAX_ARRAY_LENGTH;
}
return Integer.MAX_VALUE;
}
看到这里我们豁然开朗,也感慨Java语言设计者的良苦用心,联系上文grow()函数中传入的两个参数
那么什么情况是动态数组长度的最大值呢,我们这里可以看到是Integer.MAX_VALUE,也就是Integer长度的上界,至于原因是为什么呢,笔者的理解是动态数组是采用Integer作为下表采集的,若超出,则会发生不可预知的错误。
如果要改大,可以将Integer改为其他数据类型,重新生成自己的数据结构,当然这涉及到很多应用上的问题,这里笔者就不赘述了。
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
这里采用的是覆盖删除的方式,即把要删除的i结点右面的元素利用Copy的方式统一完成移动,时间复杂度为O(n);
查是比较简单的部分了,因为 ArrayList是基于索引的数据接口,它的底层是类似顺序表的动态数组。它可以以O(1)时间复杂度对元素进行随机访问。
其实改和查一样,基于顺序表的结构这俩个操作都十分简单,优化也没有大的优化方向了,直接看源码吧
public E set(int index, E element) {
Objects.checkIndex(index, size);
checkForComodification();
E oldValue = root.elementData(offset + index);
root.elementData[offset + index] = element;
return oldValue;
}
我们看到这里先对输入数据进行了合法性检验,而后利用checkForComodification()保证修改和查询两个线程的一致性,最后进行修改。
/**
* Appends the specified element to the end of this list.
*
* 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);
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);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
这里插入因为具有head、tail指针的缘故,所以插入始终保持o(1)的复杂度;
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* Unlinks non-null node x.
*/
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;
}
删除的源码因为要用到equals方法,所以对空指针进行了校验,同时保证是否存在空指针的元素,若存在,则对其进行删除;删除则是采用双端链表最基础的删除方式
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
因为其实LinkedList没有下标这个概念,所以其实set方法用的也不多,这里用到的lastReturned,是实现迭代器的一部分,可以理解为上一次访问到的位置,这里主要应用于提高访问效率
/**
* Returns {@code true} if this list contains the specified element.
* More formally, returns {@code true} if and only if this list contains
* at least one element {@code e} such that
* {@code Objects.equals(o, e)}.
*
* @param o element whose presence in this list is to be tested
* @return {@code true} if this list contains the specified element
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index {@code i} such that
* {@code Objects.equals(o, get(i))},
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
查找便是从头节点一直查找到尾节点,引入空指针校验,时间复杂度为O(n)
综上,我们可以做出如下总结:
线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。补充:LinkedList同样是非线程安全,只可以在单线程环境下使用。
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
而ArrayList的扩容机制我已经在上文的add()方法中书写得比较完整了,如果还有不清楚的uu可以去上文查看或留言询问。
Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。