Here is short benchmark that roughly compares various methods to remove elements from array list. Removal by index is really a bad idea since array list shrinks its internal array on every operation. There will be no real difference for small arrays but if you have large chunk of indices to remove it will cost much.
Benchmark Mode Cnt Score Error Units
CollectionsTest.filterToNewListViaLoop avgt 2 4.425 ms/op
CollectionsTest.filterToNewListViaStream avgt 2 5.410 ms/op
CollectionsTest.removeByIndexInReverseOrder avgt 2 69.234 ms/op
CollectionsTest.removeByIterator avgt 2 4.311 ms/op
CollectionsTest.removeViaRemoveAllMethod avgt 2 4.145 ms/op
CODE:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 2)
@Measurement(iterations = 2)
public class CollectionsTest {
private static final Random RANDOM = new Random();
private static final int CONTAINER_SIZE = 100_000;
private static final int REMOVE_COUNT = 10_000;
private List _container;
private TreeSet _indicesToRemove;
private HashSet _elementsToRemove;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(CollectionsTest.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
@Setup(Level.Invocation)
public void setup() {
_container = generateContainerData(CONTAINER_SIZE);
_indicesToRemove = generateIndicesToRemove(REMOVE_COUNT, CONTAINER_SIZE);
_elementsToRemove = new HashSet<>();
_indicesToRemove.stream().map(Foo::new).forEach(_elementsToRemove::add);
}
@Benchmark
public void removeByIndexInReverseOrder() {
int iterations = 0;
for (int arrayIndex : _indicesToRemove.descendingSet()) {
_container.remove(arrayIndex);
}
}
@Benchmark
public void removeByIterator() {
_container.removeIf(foo -> _elementsToRemove.contains(foo));
}
@Benchmark
public void filterToNewListViaStream() {
List elementsToKeep = _container.stream()
.filter(foo -> !_indicesToRemove.contains(foo.id))
.collect(Collectors.toList());
}
@Benchmark
public void filterToNewListViaLoop() {
List elementsToKeep = new ArrayList<>(CONTAINER_SIZE - REMOVE_COUNT);
for (Foo foo : _container) {
if (!_indicesToRemove.contains(foo.id)) elementsToKeep.add(foo);
}
}
@Benchmark
public void removeViaRemoveAllMethod() {
_container.removeAll(_elementsToRemove);
}
private List generateContainerData(int size) {
List data = new ArrayList<>();
for (int idx = 0; idx < size; idx++) {
data.add(new Foo(idx));
}
return data;
}
private TreeSet generateIndicesToRemove(int count, int containerSize) {
TreeSet data = new TreeSet<>();
ThreadLocalRandom.current().ints(0, containerSize)
.distinct()
.limit(count)
.forEach(data::add);
return data;
}
public static class Foo implements Comparable {
private static final Comparator COMPARATOR = Comparator.comparing(foo -> foo.id);
public int id;
public Foo(int id) {
this.id = id;
}
@Override
public int compareTo(Foo that) {
return COMPARATOR.compare(this, that);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Foo{");
sb.append("id=").append(id);
sb.append('}');
return sb.toString();
}
}
}