Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream list collector memory allocation speed vs loop with preallocation

I am wondering how Java 8 streams handle memory allocation if the terminal operation is a list collector.

Consider for example

List<Integer> result = myList.stream().map(doWhatever).collect(Collectors.toList());

vs

List<Integer> result = new ArrayList<>(myList.size());
for(String s : myList) {
  result.add(doWhatever.apply(s));
}

In case of using a stream, it is unknown how large the list will grow, which means there must be some kind of reallocation. Is this assumption true?

Is the type of the resulting list some kind of linked list and gives therefore slower access to elements than the ArrayList?

Should I not use streams with list collectors if I know the size of the resulting list from the beginning on?

like image 387
Tobi Avatar asked Nov 30 '16 16:11

Tobi


People also ask

Is Java 8 stream faster than for loop?

The short version basically is, if you have a small list; for loops perform better, if you have a huge list; a parallel stream will perform better. And since parallel streams have quite a bit of overhead, it is not advised to use these unless you are sure it is worth the overhead.

Which is faster stream or for loop in Java?

If you have a small list, loops perform better. If you have a huge list, a parallel stream will perform better. Purely thinking in terms of performance, you shouldn't use a for-each loop with an ArrayList, as it creates an extra Iterator instance that you don't need (for LinkedList it's a different matter).

Which is faster stream or collection?

For this particular test, streams are about twice as slow as collections, and parallelism doesn't help (or either I'm using it the wrong way?).

Are Java 8 streams slower?

To be clear, streams introduced in Java 8 were slow and the comparison from the title started to arise in many forms. Still the advantages were clear and once Java 11 came, streams were greatly optimized.


1 Answers

Behind the scenes Collectors.toList() will allow to collect the resulting elements of your Stream into an ArrayList created with the default constructor so with a default capacity of 10 so indeed a reallocation will be required in case the size exceeds 10.

If you want to use a different List's implementation, use toCollection(Supplier<C> collectionFactory) which is a more generic collector allowing to provide the factory of your target Collection.

For example, if you want to collect the elements into a LinkedList instead, you could rewrite your code as next:

List<Integer> result = myList.stream()
    .map(doWhatever)
    .collect(Collectors.toCollection(LinkedList::new));

Assuming that you want an ArrayList with a default capacity of 100, the collector would be Collectors.toCollection(() -> new ArrayList<>(100)).

like image 190
Nicolas Filotto Avatar answered Oct 18 '22 08:10

Nicolas Filotto