Understanding the Differences Between ArrayList and LinkedList
Introduction
Collections serve as containers for data storage, and each type of collection has its own strengths and weaknesses due to their underlying data structures. This article compares ArrayList and LinkedList implementations in Java.
ArrayList
ArrayList implements the List interface and maintains an ordered, duplicate-friendly structure based on an array. It dynamically resizes itself when elements are added or removed.
- When instantiated with no parameters, memory allocation is deferred until the first element is inserted.
- When initialized with a specific capacity, memory is allocated immediately.
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
Object[] elementData = null;
private int size;
public ArrayList() {
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
Adding Elements and Resizing
Adding elements at the end triggers resizing if necesssary:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
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) {
if (minCapacity - elementData.length > 0)
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);
}
Inserting at a specific index involves shifting elements:
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++;
}
Removing Elements
Removing an element requires shifting subsequent elements:
public E remove(int index) {
rangeCheck(index);
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}
Retrieving Elements
Accessing elements by index offers O(1) time complexity:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
LinkedList
LinkedList also implements the List interface and uses a doubly-linked list structure.
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Adding Elements
Adding to the end:
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++;
}
Inserting at a specified position involves traversal:
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++;
}
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;
}
}
Removing Elements
Removing an element involvse traversing the list:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
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--;
return element;
}
Retrieving Elements
Accessing elements requires traversal, except for head and tail operations which are O(1):
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
Summary
Both ArrayList and LinkedList are linear data structures implementing List. They differ in underlying implementation:
- ArrayList uses arrays; LinkedList uses a doubly-linked list.
- ArrayList performs better during iteration but slower during insertions/deletions.
- LinkedList is efficient for insertions/deletions but less so for iteration.
Use ArrayList when frequent access and appending/removing from the end are common. Use LinkedList when frequent insertions/deletions are required.