在Java中,ArrayList和LinkedList是两种常见的集合类。它们都实现了List接口,提供了类似数组的功能,可以存储任意类型的对象。虽然它们都可以实现相同的功能,但是它们的底层实现方式有所不同,因此在性能和用途上也存在一些差异。
ArrayList
ArrayList是一个基于数组实现的动态数组,它可以自动扩展容量以容纳新的元素。在ArrayList中,元素存储在一个Object数组中,可以通过索引访问数组中的元素。
底层原理
ArrayList底层使用数组实现,每个ArrayList实例都包含一个Object类型的数组elementData,用于存储元素。当ArrayList中的元素数量超过数组容量时,ArrayList会自动扩容,创建一个新的数组,并将原数组中的元素复制到新数组中。
private transient Object[] elementData;
在ArrayList中,添加元素的操作可以分为两种情况:
- 如果数组容量足够,直接将元素添加到数组末尾。
- 如果数组容量不足,需要先对数组进行扩容,然后将元素添加到数组末尾。
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) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
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);
}
在ArrayList中,删除元素的操作可以分为两种情况:
- 如果要删除的元素在数组中间,需要将后续的元素向前移动,覆盖要删除的元素。
- 如果要删除的元素在数组末尾,直接将数组长度减一即可。
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);
}
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
特点
- ArrayList是一个基于数组实现的动态数组,可以自动扩容以容纳新的元素。
- 在ArrayList中,元素可以通过索引访问,因此随机访问效率较高。
- 在ArrayList中,插入和删除操作可能需要移动后续的元素,因此效率较低。
源码
ArrayList的源码可以在JDK中找到,位于java.util包中。
LinkedList
LinkedList是一个基于链表实现的双向链表,它可以动态地添加、删除元素。在LinkedList中,元素存储在一个节点(Node)中,每个节点包含一个元素和前后指针。
底层原理
LinkedList底层使用双向链表实现,每个LinkedList实例都包含一个Node类型的first和last节点,用于表示链表的头和尾。
java
Copy
private transient Node first;
private transient Node last;
在LinkedList中,添加元素的操作可以分为三种情况:
- 如果链表为空,将新节点设置为first和last节点。
- 如果要添加的元素在链表头部,将新节点设置为first节点,并将原first节点作为新节点的后继。
- 如果要添加的元素在链表尾部,将新节点设置为last节点,并将原last节点作为新节点的前驱。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
在LinkedList中,删除元素的操作可以分为三种情况:
- 如果要删除的元素是first节点,将first节点设置为原first节点的后继。
- 如果要删除的元素是last节点,将last节点设置为原last节点的前驱。
- 如果要删除的元素在链表中间,将要删除的节点的前驱和后继节点连接起来,然后将要删除的节点的前驱和后继节点分别指向要删除节点的前驱和后继节点。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node x) {
final E element = x.item;
final Node next = x.next;
final Node 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是一个基于链表实现的双向链表,可以动态地添加、删除元素。
- 在LinkedList中,元素不能通过索引访问,因此随机访问效率较低。
- 在LinkedList中,插入和删除操作只需要改变相邻节点的指针,因此效率较高。
源码
LinkedList的源码可以在JDK中找到,位于java.util包中。
ArrayList和LinkedList的比较
从底层实现上来看,ArrayList和LinkedList有以下区别:
- ArrayList底层使用数组实现,LinkedList底层使用链表实现。
- 在ArrayList中,元素可以通过索引访问,因此随机访问效率较高,但是插入和删除操作效率较低。在LinkedList中,元素不能通过索引访问,因此随机访问效率较低,但是插入和删除操作效率较高。
- 在ArrayList中,添加和删除操作需要移动后续的元素,因此效率较低。在LinkedList中,添加和删除操作只需要改变相邻节点的指针,因此效率较高。
- 在ArrayList中,扩容时需要创建一个新的数组,并将原数组中的元素复制到新数组中,效率较低。在LinkedList中,不需要扩容,因为链表可以动态地添加、删除节点。
由于ArrayList和LinkedList的底层实现方式不同,因此它们适用的场景也不同:
- 如果需要频繁访问集合中的元素,并且集合的大小不会经常改变,选择ArrayList会更加合适,因为ArrayList可以通过索引快速访问元素,效率较高。
- 如果需要频繁添加、删除集合中的元素,选择LinkedList会更加合适,因为LinkedList可以快速地添加、删除节点,效率较高。
总结
ArrayList和LinkedList是Java中常见的集合类,它们都实现了List接口,提供了类似数组的功能,可以存储任意类型的对象。ArrayList底层使用数组实现,LinkedList底层使用链表实现。