1. 他们的底层结构不同
ArrayList 底层是基于数组实现的,ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。
- LinkedList 底层是基于链表实现的,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
- 正因为底层数据结构的不同,他们适用的场景不同,ArrayList 更适合随机查找,LinkedList 更适合删除和添加,查询、添加、删除的时间复杂度不同。
2. ArrayList 和 LinkedList 都实现了 List 接口
但是LinkedList还额外实现了Deque接口,正因为 LinkedList 实现了 Deque 接口,所以LinkedList 还可以当作队列来使用。
ArrayList 继承 AbstractList 类,实现 List 等接口
LinkedList 继承 AbstractSequentialList 类,实现 List 和 Deque 等接口
3. 查询的对比
- 指定下标进行查询,ArrayList 优于 LinkedList 。
是由于底层数据结构的原因,数组是提前分配好内存空间的。 对于链表来说,指定下标来查询,是需要遍历链表。
3.1 ArrayList 类中的查询
ArrayList 类中的 get() 方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
3.2 LinkedList 类中的查询
LinkedList 类中的 get() 方法 和 node() 方法
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
3.2.1 LinkedList 在查询时存在一种特殊情况
LinkedList 在获取第一个元素和最后一个元素时很快
原因在于,LinkedList 内部有两个属性,分别是 first 和 last 。 这两个属性一直持续的记录着,底层链表里面的第一个元素的位置和最后一个元素的位置在哪里。
transient Node<E> first;
transient Node<E> last;
所以 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;
}
4. 添加的对比
4.1 ArrayList 的添加操作
ArrayList 的添加操作是存在扩容的情况,并且对于 ArrayList 的添加操作要分情况考虑。
4.1.1 在最后的位置添加元素
不需要移动,需要扩容。
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
4.1.2 在指定位置添加元素
指定位置的元素及后面的元素,均往后移动;效率相对较慢。(因为指定了位置,所以不用去遍历查找,但是需要移动元素)
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++;
}
4.2 LinkedList 的添加操作
LinkedList 的添加操作不涉及扩容
4.2.1 在最后的位置添加元素
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++;
}
4.2.2 在指定位置添加元素
需要遍历找到下标,但是插入很快。
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++;
}
5. 总结
5.1 以下情况使用 ArrayList
- 频繁访问列表中的某一个元素。
- 只需要在列表末尾进行添加和删除元素操作。
5.2 以下情况使用 LinkedList
- 你需要通过循环迭代来访问列表中的某些元素。
- 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。
|