When to use LinkedList over ArrayList in Java?

2018-12-30 23:32发布

I've always been one to simply use:

List<String> names = new ArrayList<>();

I use the interface as the type name for portability, so that when I ask questions such as these I can rework my code.

When should LinkedList be used over ArrayList and vice-versa?

30条回答
心情的温度
2楼-- · 2018-12-30 23:40

I know this is an old post, but I honestly can't believe nobody mentioned that LinkedList implements Deque. Just look at the methods in Deque (and Queue); if you want a fair comparison, try running LinkedList against ArrayDeque and do a feature-for-feature comparison.

查看更多
素衣白纱
3楼-- · 2018-12-30 23:40

TL;DR due to modern computer architecture, ArrayList will be significantly more efficient for nearly any possible use-case - and therefore LinkedList should be avoided except some very unique and extreme cases.


In theory, LinkedList has an O(1) for the add(E element)

Also adding an element in the mid of a list should be very efficient.

Practice is very different, as LinkedList is a Cache Hostile Data structure. From performance POV - there are very little cases where LinkedList could be better performing than the Cache-friendly ArrayList.

Here are results of a benchmark testing inserting elements in random locations. As you can see - the array list if much more efficient, although in theory each insert in the middle of the list will require "move" the n later elements of the array (lower values are better):

enter image description here

Working on a later generation hardware (bigger, more efficient caches) - the results are even more conclusive:

enter image description here

LinkedList takes much more time to accomplish the same job. source Source Code

There are two main reasons for this:

  1. Mainly - that the nodes of the LinkedList are scattered randomly across the memory. RAM ("Random Access Memory") isn't really random and blocks of memory need to be fetched to cache. This operation takes time, and when such fetches happen frequently - the memory pages in the cache need to be replaced all the time -> Cache misses -> Cache is not efficient. ArrayList elements are stored on continuous memory - which is exactly what the modern CPU architecture is optimizing for.

  2. Secondary LinkedList required to hold back/forward pointers, which means 3 times the memory consumption per value stored compared to ArrayList.

DynamicIntArray, btw, is a custom ArrayList implementation holding Int (primitive type) and not Objects - hence all data is really stored adjacently - hence even more efficient.

A key elements to remember is that the cost of fetching memory block, is more significant than the cost accessing a single memory cell. That's why reader 1MB of sequential memory is up to x400 times faster than reading this amount of data from different blocks of memory:

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Source: Latency Numbers Every Programmer Should Know

Just to make the point even clearer, please check the benchmark of adding elements to the beginning of the list. This is a use-case where, in-theory, the LinkedList should really shine, and ArrayList should present poor or even worse-case results:

enter image description here

Note: this is a benchmark of the C++ Std lib, but my previous experience shown the C++ and Java results are very similar. Source Code

Copying a sequential bulk of memory is an operation optimized by the modern CPUs - changing theory and actually making, again, ArrayList/Vector much more efficient


Credits: All benchmarks posted here are created by Kjell Hedström. Even more data can be found on his blog

查看更多
千与千寻千般痛.
4楼-- · 2018-12-30 23:40

ArrayList extends AbstractList and implements the List Interface. ArrayList is dynamic array.
It can be said that it was basically created to overcome the drawbacks of arrays

The LinkedList class extends AbstractSequentialList and implements List,Deque, and Queue interface.
Performance
arraylist.get() is O(1) whereas linkedlist.get() is O(n)
arraylist.add() is O(1) and linkedlist.add() is 0(1)
arraylist.contains() is O(n) andlinkedlist.contains() is O(n)
arraylist.next() is O(1) and linkedlist.next() is O(1)
arraylist.remove() is O(n) whereas linkedlist.remove() is O(1)
In arraylist
iterator.remove() is O(n)
whereas In linkedlist
iterator.remove()is O(1)

查看更多
余生无你
5楼-- · 2018-12-30 23:42

ArrayList is essentially an array. LinkedList is implemented as a double linked list.

The get is pretty clear. O(1) for ArrayList, because ArrayList allow random access by using index. O(n) for LinkedList, because it needs to find the index first. Note: there are different versions of add and remove.

LinkedList is faster in add and remove, but slower in get. In brief, LinkedList should be preferred if:

  1. there are no large number of random access of element
  2. there are a large number of add/remove operations

=== ArrayList ===

  • add(E e)
    • add at the end of ArrayList
    • require memory resizing cost.
    • O(n) worst, O(1) amortized
  • add(int index, E element)
    • add to a specific index position
    • require shifting & possible memory resizing cost
    • O(n)
  • remove(int index)
    • remove a specified element
    • require shifting & possible memory resizing cost
    • O(n)
  • remove(Object o)
    • remove the first occurrence of the specified element from this list
    • need to search the element first, and then shifting & possible memory resizing cost
    • O(n)

=== LinkedList ===

  • add(E e)

    • add to the end of the list
    • O(1)
  • add(int index, E element)

    • insert at specified position
    • need to find the position first
    • O(n)
  • remove()
    • remove first element of the list
    • O(1)
  • remove(int index)
    • remove element with specified index
    • need to find the element first
    • O(n)
  • remove(Object o)
    • remove the first occurrence of the specified element
    • need to find the element first
    • O(n)

Here is a figure from programcreek.com (add and remove are the first type, i.e., add an element at the end of the list and remove the element at the specified position in the list.):

enter image description here

查看更多
孤独总比滥情好
6楼-- · 2018-12-30 23:42

One of the tests I saw on here only conducts the test once. But what I have noticed is that you need to run these tests many times and eventually their times will converge. Basically the JVM needs to warm up. For my particular use case I needed to add/remove items to a last that grows to about 500 items. In my tests LinkedList came out faster, with linked LinkedList coming in around 50,000 NS and ArrayList coming in at around 90,000 NS... give or take. See the code below.

public static void main(String[] args) {
    List<Long> times = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        times.add(doIt());
    }
    System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}

static long doIt() {
    long start = System.nanoTime();
    List<Object> list = new LinkedList<>();
    //uncomment line below to test with ArrayList
    //list = new ArrayList<>();
    for (int i = 0; i < 500; i++) {
        list.add(i);
    }

    Iterator it = list.iterator();
    while (it.hasNext()) {
        it.next();
        it.remove();
    }
    long end = System.nanoTime();
    long diff = end - start;
    //uncomment to see the JVM warmup and get faster for the first few iterations
    //System.out.println(diff)
    return diff;
}
查看更多
梦该遗忘
7楼-- · 2018-12-30 23:43

Joshua Bloch, the author of LinkedList:

Does anyone actually use LinkedList? I wrote it, and I never use it.

Link: https://twitter.com/joshbloch/status/583813919019573248

I'm sorry for the answer for being not that informative as the other answers, but I thought it would be the most interesting and self-explanatory.

查看更多
登录 后发表回答