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?
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.
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).
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?).
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.
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))
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With