从流中获取最后 n 个元素

Get last n elements from stream

我想知道是否有替代方法

List<X> lastN = all.subList(Math.max(0, all.size() - n), all.size());

stream 用法?

使用Stream.skip()

Returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream. If this stream contains fewer than n elements then an empty stream will be returned.

all.stream().skip(Math.max(0, all.size() - n)).forEach(doSomething);

如果流的大小未知,则可能无法使用整个流并缓冲目前遇到的最后 n 个元素。您可以使用某种双端队列或自动保持其最大大小的专用环形缓冲区来执行此操作(有关某些实现,请参见this related question)。

public static <T> List<T> lastN(Stream<T> stream, int n) {
    Deque<T> result = new ArrayDeque<>(n);
    stream.forEachOrdered(x -> {
        if (result.size() == n) {
            result.pop();
        }
        result.add(x);
    });
    return new ArrayList<>(result);
}

所有这些操作(sizepopadd)的复杂度应该是 O(1),所以整体具有(未知)长度 n 的流的复杂性将是 O(n).

自定义收集器可以这样写:

public static <T> Collector<T, ?, List<T>> lastN(int n) {
    return Collector.<T, Deque<T>, List<T>>of(ArrayDeque::new, (acc, t) -> {
        if(acc.size() == n)
            acc.pollFirst();
        acc.add(t);
    }, (acc1, acc2) -> {
        while(acc2.size() < n && !acc1.isEmpty()) {
            acc2.addFirst(acc1.pollLast());
        }
        return acc2;
    }, ArrayList::new);
}

并像这样使用它:

List<String> lastTen = input.stream().collect(lastN(10));

有时我需要一个 "oneliner"(在本例中是三个衬里),因为创建一个收集器太麻烦了。

如果流很小,则可以再次 reverselimitreverse 而不会牺牲性能。这将产生最后 n 个元素。

如果需要过滤,这很有用,因为在这种情况下无法指定大小。

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
  .filter(i -> i % 2 == 0)
  .sorted(Comparator.reverseOrder())
  .limit(2)
  .sorted(Comparator.naturalOrder())
  .forEach(System.out::println); // prints 6 8