博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Lambda : items.removeIf()
阅读量:5925 次
发布时间:2019-06-19

本文共 7459 字,大约阅读时间需要 24 分钟。

hot3.png

In Java 8, you can do:

items.removeIf(i -> predicate(i));

Where you used to do:

for (Iterator it = items.iterator(); it.hasNext();) {  if (predicate(it.next())) {    it.remove();   } }

Which is not only considerably more elegant but also a more efficient solution in some cases. I have the graphs to prove it!

Background

Recently having spent a few years away from Java development, it is great to be back and find that Java 8 indeed has delivered on what was promised in 2013. With lambdas and streams, it is possible to tackle problems in a functional manner and write code that is both readable and efficient. This over due modernization of the language is by far the most important change in Java 8, but there are also other improvements that deserve mentioning. This post elaborates on the topic of Collection.removeIf() which allows for conditional removal of elements of a collection.

The Way of the Iterator

Removing elements of a collection while iterating over it is somewhat like cutting off the branch on which you are sitting, but by using Iterators that can be done without a runtime ConcurrentModificationException. Consider the following typical simplistic example, where the elements of the collection items for which predicate() holds true are removed.

for (Iterator it = items.iterator(); it.hasNext();) {  if (predicate(it.next())) {    it.remove();   } }

If the collection in question is a linked list of size n and m elements are removed, the full iteration with removals is O(n + m) = O(n) since iteration steps and removal from the list are constant time operations and m is limited by n. If, however, the data structure of the collection is one where removal is relatively expensive compared to iteration, the complexity becomes less favorable. For an ArrayList a single removal step is O(n) since all items at higher indices have to be moved. There are still n iteration steps to be made so the full sequence of removals becomes O(m*n) which is O(n^2) assuming worst case with the number of removals being a constant fraction of the array size.

In more pragmatic terms, what happens in the ArrayList case is that for each element that is removed, all elements of higher indices are moved to fill the gap in the backing array. This is clearly suboptimal since that operation is repeated for each removal. Thus, to reach its final destination an element will visit all locations between the starting position and the destination. In case many elements are removed, this may be a considerable amount of work in vain.

The Java 8 Way of the Lambda

In Java 8, the code snippet from above can be expressed as follows.

items.removeIf(i -> predicate(i));

Clearly more concise, this expression is also more efficient on an ArrayList compared to the Iterator approach. Where the Iterator requires completion of each removal operation before continuing to the next one, removeIfoperates on a predicate and can therefore iterate over all items once to determine which elements are to be removed and then shift the remaining elements to the correct new indices all in a second pass. Therefore, removeIf is O(n) since it performs two linear passes through the collection. Each retained element in the resulting collection is moved just once, while for the Iterator approach it would be moved one step at a time towards its destination. See  for the relevant JDK code.

The Difference Visualized

We compare execution times for the two approaches as a function of the array size for a rather extreme case where 999 out of 1000 items are removed from an ArrayList. For each data point 40 runs are made, the 5 fastest and slowest outliers are removed and the sum of the remaining 30 form the execution time value. For details please refer to .

We expect the execution time to be considerably shorter for the Java 8 way of the lambda and as a matter of fact that is what we find. We also note that within the resolution of the graph, the lines are quite noise free due to the many repeats and filtering of outlier results.

 

For array sizes of 150,000 elements, the removeIf() approach is more than 3000 times faster. Even more importantly, the factor between the two increases as the size of the array increases which is expected since the way of the lambda has a superior asymptotical time complexity.

To better compare the time complexity of the two, we scale up the green line to match the green one. By multiplying the execution times of the faster algorithm by ~1500, the graphs can be compared in terms of shape. As expected, we find linear behavior for the Java 8 removeIf() design, while the old ways show a quadratic time complexity due to the inefficient removal of elements.

When scaling up the measurements we get to see some noise in the green line. The sensitivity to random fluctuations in execution time is greater since the execution times are several orders of magnitude shorter compared to the blue. Both lines are fitted to polynomials of power two and as can be clearly seen, the light green line is very similar to a straight line while the blue line clearly aims for the sky in a super linear fashion.

Edit: There is a  about this blog post. Good points are being made there, for example the fact that the removeIf implementation is atomic (in the database sense) as it first evaluates the predicate for all items before making any alterations to the list. Thus, if the predicate throws an exception for any of the items in the list, the list will remain unchanged.

 

/* * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * */package java.util;...public class ArrayList
extends AbstractList
implements List
, RandomAccess, Cloneable, java.io.Serializable{ ... @Override public boolean removeIf(Predicate
filter) { Objects.requireNonNull(filter); // figure out which elements are to be removed // any exception thrown from the filter predicate at this stage // will leave the collection unmodified int removeCount = 0; final BitSet removeSet = new BitSet(size); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { @SuppressWarnings("unchecked") final E element = (E) elementData[i]; if (filter.test(element)) { removeSet.set(i); removeCount++; } } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } // shift surviving elements left over the spaces left by removed elements final boolean anyToRemove = removeCount > 0; if (anyToRemove) { final int newSize = size - removeCount; for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { i = removeSet.nextClearBit(i); elementData[j] = elementData[i]; } for (int k=newSize; k < size; k++) { elementData[k] = null; // Let gc do its work } this.size = newSize; if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; } return anyToRemove; } ... }

 

转载于:https://my.oschina.net/u/2935389/blog/895098

你可能感兴趣的文章
《vSphere性能设计:性能密集场景下CPU、内存、存储及网络的最佳设计实践》一1.2.2 内存...
查看>>
《版式设计——日本平面设计师参考手册》—第1章应用对象样式
查看>>
《vSphere性能设计:性能密集场景下CPU、内存、存储及网络的最佳设计实践》一1.1.1 确定参数...
查看>>
《编写高质量代码:改善c程序代码的125个建议》——建议14-2:在右移中合理地选择0或符号位来填充空出的位...
查看>>
Hibernate统计表中的条数
查看>>
9 个使用前必须再三小心的 Linux 命令
查看>>
《我的视频我做主:Premiere Pro CS5实战精粹》——第一部分 基础篇 第1章 非线性剪辑基础 1.1 认识非线性剪辑...
查看>>
物理专线流量平滑切换
查看>>
《互联网+流通——F2R助力传统产业创新与转型》一一第1章 “互联网+”的新时代...
查看>>
《音乐达人秀:Adobe Audition实战200例》——实例11 录制任意音量音乐
查看>>
《SAP入门经典(第4版•修订版)》——2.5 4种视角相互结合
查看>>
7 种 JavaScript 技巧使你更聪明
查看>>
Devuan Jessie beta 释出
查看>>
《Network Warrior中文版(第2版)——思科网络工程师必备手册》一3.3 自动协商故障...
查看>>
《大型网站服务器容量规划》一1.1 容量规划背景
查看>>
开源防火墙解决方案
查看>>
深入剖析阿里云推荐引擎——新架构,新体验
查看>>
辨别真假数据科学家必备手册:深度学习45个基础问题(附答案)
查看>>
Ubuntu 每日技巧- 自动备份Ubuntu 14.04到Box云存储上
查看>>
在 Linux 下使用 RAID(二):使用 mdadm 工具创建软件 RAID 0 (条带化)
查看>>