过滤器 Java 没有外部库的就地列表
Filter Java List in place without external libraries
这个问题类似于 What is the best way to filter a Java Collection? "filter a java.util.Collection
based on a predicate." 有额外的要求
- 过滤器就地完成(
O(1)
内存,不包括输入)因为列表很大
- 不得使用外部库(即 Guava、Apache commons 等)
- Java 7 兼容(无 Java 8 流)
我们可以假设 java.util.Collection
类型是实现 .remove(int)
的 java.util.List
可能的解决方案:
- 对
List
的 Iterator
使用 .remove()
方法。这可能会引发 UnsupportedOperationException
,因为 .remove()
方法在 Iterator
上是可选的支持
- 编写我们自己的迭代器,使用索引
.size()
和 .remove(int)
遍历列表
有没有更简单的解决方案?
是否为所有标准 Java List
实施 and/or Collection
实施 .remove(int)
?
Filters
和 Predicate
是 Java8 类型,所以如果你不想使用 Java8,你需要类似的东西。
您可以使用包装 Iterator
伪造过滤器并使其与对象一起使用(类似于 Prediates 的实现方式);但是,还有一些次要问题:
你说列表很大,解决方案的内存影响应该是O(1)
,但如果不知道正在操作的列表,就无法保证这样的事情。 remove(int)
运算符可以在实现中分配一个新的列表索引并复制到其中。
假设列表没有这样的事情,你能做的最好的事情就是实现你自己的迭代器,它接受类似测试的谓词,或者编写一个特定的循环来处理列表。
无论如何,这听起来像是一道面试题。这是一个例子
public interface MyPredicate<T> {
public boolean isTrue(T value);
}
public void removeOnTrue(List<T> list, MyPredicate<T> predicate) {
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
T next = iterator.next();
if (predicate.isTrue(next)) {
iterator.remove();
}
}
}
使用跨索引的 for 循环执行此操作大致相同,只是您随后必须跟踪索引(并使用索引删除)。
要使用上面的例子:
...
List<String> names = ...;
removeOnTrue(names, new MyPredicate<String>() {
public boolean isTrue(String value) {
return value.startsWith("A");
}
});
...
将产生一个 names
并删除所有以 "A" 开头的字符串。
没有适合所有 List
的最佳解决方案,这就是您永远无法达到 Java8 效率的地方,因为作为 interface
方法,Java 8 的 default
方法可以被任何 List
实现覆盖,提供为特定 class.
量身定制的实现
当你想在Java 8之前的版本中合理实现类似功能时,你必须关注常见情况。几乎没有 JRE 提供的 remove(int)
适用但 Iterator.remove
不适用 1 的列表。但考虑到 ArrayList
是最常用的可变 List
实现,对于该实现,基于迭代器的解决方案对于大型列表和大量已删除项目的性能不佳。这是因为无论您使用 remove(int)
还是 Iterator.remove
,每个删除操作都会在您继续之前将所有后续项目移动一个位置,并且可能会再次删除一个项目。在最坏的情况下,有一个谓词匹配所有项目,这将带来二次复杂性。因此,为这种情况提供更复杂的解决方案很重要:
interface Predicate<T> {
boolean test(T object);
}
public static <T> boolean removeIf(List<T> list, Predicate<? super T> p) {
if(list instanceof RandomAccess) {
int num=list.size();
BitSet bs=new BitSet(num);
for(int index=0; index<num; index++) {
if(p.test(list.get(index))) bs.set(index);
}
if(bs.isEmpty()) {
return false;
}
for(int dst=bs.nextSetBit(0), src=dst;; dst++, src++) {
src=bs.nextClearBit(src);
if(src==num) {
list.subList(dst, src).clear();
break;
}
list.set(dst, list.get(src));
}
return true;
}
else {
boolean changed=false;
for(Iterator<T> it=list.iterator(); it.hasNext(); ) {
if(p.test(it.next())) {
it.remove();
changed=true;
}
}
return changed;
}
}
在列表实现 RandomAccess
的情况下,它包括所有 arraylist 样式实现,该解决方案将模仿类似于 Java 8 的 ArrayList.removeIf
实现的东西,尽管我们没有直接的访问内部数组,我遗漏了所有快速失败并发修改检测的东西。现在,对于 ArrayList
类型的列表,它将具有线性复杂度,因此对于 LinkedList
,它将具有线性复杂度,因为它没有实现 RandomAccess
,因此将使用其 [=26] 进行处理=].
该方法还实现了 Java 8 的 removeIf
返回列表是否已被操作更改的方法的约定。
1 CopyOnWriteArrayList
是一个例外,但对于写时复制列表,就地 removeIf
的想法没有实际意义,除非提供通过列表本身,因为在通过其 remove(int)
(或任何其他 public
)操作实现它时,我们在每次更改时有效地复制了整个列表。所以在那种情况下,将整个列表复制到一个普通列表中,在该列表上执行 removeIf
并将其复制回来在大多数情况下会更有效率。
这个问题类似于 What is the best way to filter a Java Collection? "filter a java.util.Collection
based on a predicate." 有额外的要求
- 过滤器就地完成(
O(1)
内存,不包括输入)因为列表很大 - 不得使用外部库(即 Guava、Apache commons 等)
- Java 7 兼容(无 Java 8 流)
我们可以假设 java.util.Collection
类型是实现 .remove(int)
java.util.List
可能的解决方案:
- 对
List
的Iterator
使用.remove()
方法。这可能会引发UnsupportedOperationException
,因为.remove()
方法在Iterator
上是可选的支持
- 编写我们自己的迭代器,使用索引
.size()
和.remove(int)
遍历列表
有没有更简单的解决方案?
是否为所有标准 Java List
实施 and/or Collection
实施 .remove(int)
?
Filters
和 Predicate
是 Java8 类型,所以如果你不想使用 Java8,你需要类似的东西。
您可以使用包装 Iterator
伪造过滤器并使其与对象一起使用(类似于 Prediates 的实现方式);但是,还有一些次要问题:
你说列表很大,解决方案的内存影响应该是O(1)
,但如果不知道正在操作的列表,就无法保证这样的事情。 remove(int)
运算符可以在实现中分配一个新的列表索引并复制到其中。
假设列表没有这样的事情,你能做的最好的事情就是实现你自己的迭代器,它接受类似测试的谓词,或者编写一个特定的循环来处理列表。
无论如何,这听起来像是一道面试题。这是一个例子
public interface MyPredicate<T> {
public boolean isTrue(T value);
}
public void removeOnTrue(List<T> list, MyPredicate<T> predicate) {
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
T next = iterator.next();
if (predicate.isTrue(next)) {
iterator.remove();
}
}
}
使用跨索引的 for 循环执行此操作大致相同,只是您随后必须跟踪索引(并使用索引删除)。
要使用上面的例子:
...
List<String> names = ...;
removeOnTrue(names, new MyPredicate<String>() {
public boolean isTrue(String value) {
return value.startsWith("A");
}
});
...
将产生一个 names
并删除所有以 "A" 开头的字符串。
没有适合所有 List
的最佳解决方案,这就是您永远无法达到 Java8 效率的地方,因为作为 interface
方法,Java 8 的 default
方法可以被任何 List
实现覆盖,提供为特定 class.
当你想在Java 8之前的版本中合理实现类似功能时,你必须关注常见情况。几乎没有 JRE 提供的 remove(int)
适用但 Iterator.remove
不适用 1 的列表。但考虑到 ArrayList
是最常用的可变 List
实现,对于该实现,基于迭代器的解决方案对于大型列表和大量已删除项目的性能不佳。这是因为无论您使用 remove(int)
还是 Iterator.remove
,每个删除操作都会在您继续之前将所有后续项目移动一个位置,并且可能会再次删除一个项目。在最坏的情况下,有一个谓词匹配所有项目,这将带来二次复杂性。因此,为这种情况提供更复杂的解决方案很重要:
interface Predicate<T> {
boolean test(T object);
}
public static <T> boolean removeIf(List<T> list, Predicate<? super T> p) {
if(list instanceof RandomAccess) {
int num=list.size();
BitSet bs=new BitSet(num);
for(int index=0; index<num; index++) {
if(p.test(list.get(index))) bs.set(index);
}
if(bs.isEmpty()) {
return false;
}
for(int dst=bs.nextSetBit(0), src=dst;; dst++, src++) {
src=bs.nextClearBit(src);
if(src==num) {
list.subList(dst, src).clear();
break;
}
list.set(dst, list.get(src));
}
return true;
}
else {
boolean changed=false;
for(Iterator<T> it=list.iterator(); it.hasNext(); ) {
if(p.test(it.next())) {
it.remove();
changed=true;
}
}
return changed;
}
}
在列表实现 RandomAccess
的情况下,它包括所有 arraylist 样式实现,该解决方案将模仿类似于 Java 8 的 ArrayList.removeIf
实现的东西,尽管我们没有直接的访问内部数组,我遗漏了所有快速失败并发修改检测的东西。现在,对于 ArrayList
类型的列表,它将具有线性复杂度,因此对于 LinkedList
,它将具有线性复杂度,因为它没有实现 RandomAccess
,因此将使用其 [=26] 进行处理=].
该方法还实现了 Java 8 的 removeIf
返回列表是否已被操作更改的方法的约定。
1 CopyOnWriteArrayList
是一个例外,但对于写时复制列表,就地 removeIf
的想法没有实际意义,除非提供通过列表本身,因为在通过其 remove(int)
(或任何其他 public
)操作实现它时,我们在每次更改时有效地复制了整个列表。所以在那种情况下,将整个列表复制到一个普通列表中,在该列表上执行 removeIf
并将其复制回来在大多数情况下会更有效率。