源码级别说明ArrayList和LinkedList的区别
首先二者都是实现了List接口的不同实现,并且都不是线程安全的
区别一 : 数据结构方面
- ArrayList底层使用的是动态数组来存储元素,初始化大小DEFAULT_CAPACITY = 10;
- LinkedList底层使用的是双向链表来存储元素
区别二 : 使用场景
- ArrayList更适用于随即查找
- LinkedList更适合删除和添加
- 再因为LinkedList在实现了List接口的基础上还实现了Deque接口,所以LinkedList还被用来做双端队列
区别三 : get方法
区别四 : 添加操作
-
ArrayList的添加操作
-
1 . arrayList.add(element),直接添加到数组的最后一个位置 -
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
-
2 . arrayList.add(index,element),添加指定位置 -
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
-
LinkedList的添加操作
-
1 . LinkedList.add(element),直接添加到数组的最后一个位置 -
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
-
2 . LinkedList.add(index,element),添加指定位置 -
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
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的扩容操作
-
ArrayList的扩容
-
例 : -
ArrayList arrayList = new ArrayList(2);
arrayList.add(1);
arrayList.add(2);
System.out.println(arrayList.add(3));
-
插入方法 -
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
-
ensureCapacityInternal()方法,就是确保可以放的下这个元素,就是检查是否需要扩容 -
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
-
grow(minCapacity)方法 -
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
-
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
区别六 : 删除操作
-
ArrayList的删除
-
public E remove(int index) {
//检查这个index是否正确
rangeCheck(index);
modCount++;
//取出index的值
E oldValue = elementData(index);
//判断要删除的那个元素是否是否是唯一的那个元素
int numMoved = size - index - 1;
//不是就删除,并且复制移动数组
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//是的话,就将数组置为空,等待GC
elementData[--size] = null; // clear to let GC do its work
//返回删除的值
return oldValue;
}
-
LinkedList的删除
总结
如上所述,即为ArrayList和LinkedList的源码层面的区别分析,那么到底在实际开发中具体使用什么呢?
- ArrayList比较适合随机查找的原因是因为底层是数组,可以直接通过index确定到具体的element,时间复杂度为0(1)
- ArrayList的增删改比较耗时原因是不仅是因为需要扩容,而且还有System.arraycopy()和Arrays.copyOf()这两个方法的存在,就是在对数组进行改动的时候,需要将数组进行复制移动,时间复杂度为0(n)
- LinkedList比较适合增删改的原因就是只是单纯的链表增删改,时间复杂度为0(1),如果是指定下标增删改的话就是0(n),因为需要遍历嘛
- LindedList查找比较耗时就是因为需要遍历链表,其实他底层还使用了二分法进行遍历优化,但是还是比较慢,时间复杂度为O(n),但是如果是查第一个或者最后一个,因为有getFirst()和getLast()这两个方法t的存在,时间复杂度为0(1)
补充知识
我们会在ArrayList和LinkedList的源码中看到很多的modCount++,这是什么呢?
modCount字面意思就是modify count 修改次数
那为什么要记录修改次数的呢?
- 我们知道ArrayList和LinkedList不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了list,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map:注意到 modCount 声明为 volatile,保证线程之间修改的可见性。
注意!!!,在JDK5和JDK6的时候,也许是正确的,因为在JDK5和JDK6中变量modCount确实声明为volatile。但在JDK7和JDK8中,已经没有这样声明了!!!!!
- JDK7和JDK8中,说的是此异常并不总是表示对象已被其他线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。 例如,如果线程使用有fail-fast机制的迭代器在集合上迭代时修改了集合,迭代器将抛出此异常。,就是说ConcurrentModificationException这个异常不一定是因为迭代器在集合上迭代时修改了集合,还有可能是因为违反对象约定的方法调用等等
写在最后
以上就是我自己通过阅读源码和构造例子对ArrayList和LinkedList区别的源码级别的说明,希望对大家有帮助
|