ArrayList vs. LinkedList for Stack implementation
This comment made me think: how does a LinkedList
fare against an ArrayList
for a Stack implementation? Sure, LinkedList
should be better for insertion and deletion... But is it? And when does it start to matter (i.e. how many objects)? So I have set up a little benchmark. First, a Stack
interface:
public interface Stack<T> { public void push(T element); public T pop(); public int size(); }
In this stack, we'll be pushing and popping Element
s:
public class Element { }
Our stack will allow us to pass either an ArrayList
or a LinkedList
as the implementation:
import java.util.List; public class GenericStack<T, U extends List<T>> implements Stack<T> { private U list; public GenericStack(U list) { this.list = list; } public T pop() { if (!list.isEmpty()) { return list.remove(0); } else throw new IndexOutOfBoundsException("Cannot pop an empty stack"); } public void push(T element) { list.add(0, element); } public int size() { return list.size(); }
The benchmark itself is the following main
:
public static void main(String[] args) { int values[] = {1000, 2000, 5000, 10000, 50000, 100000, 500000, 1000000}; GenericStack<Element, ArrayList<Element>> arrayListStack = new GenericStack<Element, ArrayList<Element>>(new ArrayList<Element>()); GenericStack<Element, LinkedList<Element>> linkedListStack = new GenericStack<Element, LinkedList<Element>>(new LinkedList<Element>()); for (int v = 0; v < values.length; v++) { int currentSize = values[v]; System.out.println("pushing " + currentSize + " elements"); System.out.println(); long start = System.currentTimeMillis(); for (int i = 0; i < currentSize; i++) { arrayListStack.push(new Element()); } double time = (System.currentTimeMillis() - start) / (float) currentSize; System.out.println("arraylist stack push: " + time); start = System.currentTimeMillis(); for (int i = 0; i < currentSize; i++) { linkedListStack.push(new Element()); } time = (System.currentTimeMillis() - start) / (float) currentSize; System.out.println("linkedlist stack push: " + time); System.out.println("popping " + currentSize + " elements"); System.out.println(); for (int i = 0; i < currentSize; i++) { arrayListStack.pop(); } time = (System.currentTimeMillis() - start) / (float) currentSize; System.out.println("arraylist stack pop: " + time); for (int i = 0; i < currentSize; i++) { linkedListStack.pop(); } time = (System.currentTimeMillis() - start) / (float) currentSize; System.out.println("linkedlist stack pop: " + time); System.out.println(); } }
And here are the results (in milliseconds):
pushing 1000 elements arraylist stack push: 0.0 linkedlist stack push: 0.0 popping 1000 elements arraylist stack pop: 0.0 linkedlist stack pop: 0.0 pushing 2000 elements arraylist stack push: 0.0 linkedlist stack push: 0.0 popping 2000 elements arraylist stack pop: 0.0 linkedlist stack pop: 0.0 pushing 5000 elements arraylist stack push: 0.0031999999191612005 linkedlist stack push: 0.0 popping 5000 elements arraylist stack pop: 0.003000000026077032 linkedlist stack pop: 0.003000000026077032 pushing 10000 elements arraylist stack push: 0.006300000008195639 linkedlist stack push: 0.0 popping 10000 elements arraylist stack pop: 0.004699999932199717 linkedlist stack pop: 0.004699999932199717 pushing 50000 elements arraylist stack push: 0.022180000320076942 linkedlist stack push: 0.0 popping 50000 elements arraylist stack pop: 0.022180000320076942 linkedlist stack pop: 0.022180000320076942 pushing 100000 elements arraylist stack push: 0.04374999925494194 linkedlist stack push: 3.1999999191612005E-4 popping 100000 elements arraylist stack pop: 0.04407000169157982 linkedlist stack pop: 0.04407000169157982 pushing 500000 elements arraylist stack push: 0.2210640013217926 linkedlist stack push: 4.0600000647827983E-4 popping 500000 elements arraylist stack pop: 0.22146999835968018 linkedlist stack pop: 0.2214999943971634 pushing 1000000 elements arraylist stack push: 0.4516749978065491 linkedlist stack push: 4.220000118948519E-4 popping 1000000 elements arraylist stack pop: 0.45311200618743896 linkedlist stack pop: 0.45315900444984436
So a significant difference only appears when pushing 10,000 elements, and after that, it degrades quite dramatically in favour of the LinkedList
.
ArrayList
that bad?
Now, it appears that we have kind of disadvantaged ArrayList
here by choosing the worst case scenario for insertion: inserting at index 0, which means that the implementation will have to enlarge the internal array if needed, shift all the objects by 1, and insert the element at 0.
So let's have more specific implementation where we'll be "pushing" at the end (and therefore "popping" at the end too). Here is our new ArrayListStack
:
import java.util.ArrayList; public class ArrayListStack<T> implements Stack<T> { private ArrayList<T> list = new ArrayList<T>(); public T pop() { if (!list.isEmpty()) { return list.remove(list.size() - 1); } else throw new IndexOutOfBoundsException("Cannot pop an empty stack"); } public void push(T element) { list.add(element); } public int size() { return list.size(); } }
To be fair, let's give LinkedList
its own implementation, with getFirst()
and removeFirst()
:
import java.util.LinkedList; public class LinkedListStack<T> implements Stack<T> { private LinkedList<T> list = new LinkedList<T>(); public T pop() { if (!list.isEmpty()) { return list.removeFirst(); } else throw new IndexOutOfBoundsException("Cannot pop an empty stack"); } public void push(T element) { list.addFirst(element); } public int size() { return list.size(); } }
And now you're in for a good surprise: here are the results!
pushing 1000 elements arraylist stack push: 0.0 linkedlist stack push: 0.0 popping 1000 elements arraylist stack pop: 0.0 linkedlist stack pop: 0.0 pushing 2000 elements arraylist stack push: 0.0 linkedlist stack push: 0.007499999832361937 popping 2000 elements arraylist stack pop: 0.007499999832361937 linkedlist stack pop: 0.007499999832361937 pushing 5000 elements arraylist stack push: 0.0 linkedlist stack push: 0.0 popping 5000 elements arraylist stack pop: 0.0 linkedlist stack pop: 0.0 pushing 10000 elements arraylist stack push: 0.0 linkedlist stack push: 0.0 popping 10000 elements arraylist stack pop: 0.0 linkedlist stack pop: 0.0 pushing 50000 elements arraylist stack push: 0.0 linkedlist stack push: 3.1999999191612005E-4 popping 50000 elements arraylist stack pop: 3.1999999191612005E-4 linkedlist stack pop: 3.1999999191612005E-4 pushing 100000 elements arraylist stack push: 1.5999999595806003E-4 linkedlist stack push: 3.100000030826777E-4 popping 100000 elements arraylist stack pop: 3.100000030826777E-4 linkedlist stack pop: 3.100000030826777E-4 pushing 500000 elements arraylist stack push: 2.800000074785203E-4 linkedlist stack push: 4.3799998820759356E-4 popping 500000 elements arraylist stack pop: 4.6999999904073775E-4 linkedlist stack pop: 4.6999999904073775E-4 pushing 1000000 elements arraylist stack push: 9.40000027185306E-5 linkedlist stack push: 4.3700000969693065E-4 popping 1000000 elements arraylist stack pop: 4.529999860096723E-4 linkedlist stack pop: 4.6800001291558146E-4
In these results, ArrayList
performs better, and after 1,000,000 the difference is actually stunning. And that's even before adding the extra optimization of sizing the list at initialization (thus preventing the overhead of redimensioning the array).
The thing is, adding at the end of an ArrayList
will perform better than LinkedList
, so this means that for a stack implementation (where you push and pop at the same end), ArrayList
is quite a good pick.
Just a note, I think you made a typo in the summary of the last test result : “In these results, LinkedList performs better”. Actually, ArrayList performs better as you point in your conclusion.
A pleasure to read, it makes me regret my java past. it’s certainly time I start yet another never-finished project to shake my rusty java-competences a bit !
— Pierre · 2009-12-15 16:09 · #
That’s right, now fixed!
Ironically, I’ve been learning C++ for the past 3 months, and quite like it!!
— sebastien · 2009-12-16 18:06 · #
Nice post. Though Finding the element in the middle of the LinkedList takes too much time as the benefits of just changing the pointer are lost. So, LinkedList is worse than ArrayList for removing elements in the middle see here for some http://javarevisited.blogspot.com/2012/02/difference-between-linkedlist-vs.html
— Javin · 2012-03-30 05:27 · #
Does this mean that for a queue an ArrayList will also be a better choice for implementation vs. a LinkedList implementation?
— Sidd · 2017-02-25 10:23 · #
Both ArrayList and LinkedList are non-synchronized and can be made synchronized explicitly. Many thanks for sharing this.
— priya · 2017-12-26 05:24 · #