在没有外部库的情况下过滤Java List

时间:2016-03-14 20:31:52

标签: java list collections java-7

此问题类似于What is the best way to filter a Java Collection?"根据谓词过滤java.util.Collection。"

附加要求
  • 过滤器应该就位(O(1)内存不包括输入),因为列表很大
  • 不能使用外部库(即Guava,Apache commons等)
  • 兼容Java 7(无Java 8流)

我们可以假设java.util.Collection类型是实现java.util.List的{​​{1}}

可能的解决方案:

  • .remove(int)的{​​{1}}上使用.remove()方法。这可能会引发Iterator,因为List
  • 可选择支持UnsupportedOperationException方法
  • 编写我们自己的迭代器,使用索引.remove()Iterator
  • 遍历列表

有没有更简单的解决方案?

是否为实施.size()的所有标准Java .remove(int)和/或Iterator.remove()实施了List

2 个答案:

答案 0 :(得分:2)

没有适合所有List的最佳解决方案,而这是您永远无法达到Java 8效率的地方,因为作为interface方法,Java 8的default方法可以被任何List实现覆盖,提供为该特定类定制的实现。

如果要在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因此将使用其{{1}进行处理}}

该方法还履行了Java 8的removeIf方法的约定,该方法返回列表是否已被操作更改。

1 Iterator是一个例外,但对于写时复制列表,就地CopyOnWriteArrayList的想法没有实际意义,除非列表本身提供,因为,当通过其removeIf(或任何其他remove(int))操作实现它时,我们有效地在每次更改时复制整个列表。因此,在这种情况下,将整个列表复制到普通列表中,在该列表上执行public并将其复制回来在大多数情况下会更有效。

答案 1 :(得分:0)

FiltersPredicate是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,所有字符串都以&#34; A&#34;开头。除去。