Java Streams中返回最后N个元素

4
这个问题只是为了好玩。
我有这个方法:
private static String getBaseDomain(String fullDomain) {
    // we take the base domain from the usual xxx.yyy.basedomain.tld: we 
    // want only the last 2 elements: basedomain.tld
    List<String> elements = Arrays.asList(fullDomain.split("\\."));
    if( elements.size() > 2){
        elements = elements.subList(elements.size()-2, elements.size());
    }
    return String.join(".", elements);
}

我想知道如何使用Java流API获得相同的结果(实际上,我想知道哪种方法是最资源高效的)。

我无法想象如何从流中仅获取最后2个元素:limit(2)会给我前两个,而对于skip(XXX),我不知道如何"内联"提取流的大小。

你能告诉我你会怎么做吗?


我建议使用某种带有队列的折叠累加器,并在其达到特定大小时从中删除元素。 - talex
4个回答

7
你可以使用skip
elements.stream().skip(elements.size() - 2)
API

返回一个包含该流在跳过前n个元素后剩余元素的流。如果此流包含少于n个元素,则将返回一个空流。

无用的例子:
// a list made of a, b, c and d
List<String> l = Arrays.asList("a", "b", "c", "d");

// prints c and d
l.stream().skip(l.size() - 2).forEach(System.out::println);

可能没用的注释:

正如一些人所提到的,这仅在你有一个可用大小的情况下才有效,即如果你从一个集合中进行流式传输。

引用Nicolas的话,流没有大小。


如果 elements 是一个 Stream,那么你不能这样做。因为你使用了它两次。 - talex
@talex 哦,是的,打错了 - 已经修正 - 这里的 elements 是一个 List - Mena
请注意,这仅适用于“集合”,而不适用于任何“流”,因为“流”没有大小。 - Nicolas Filotto
1
@NicolasFilotto 当然。但我假设这里的使用情况是从集合中进行流式传输,参见 OP 的场景。 - Mena
2
可能是双大括号反模式最糟糕的应用之一。List<String> l = Arrays.asList("a", "b", "c", "d"); 怎么样?或者如果必须使用 ArrayListList<String> l = new ArrayList<>(); Collections.addAll(l, "a", "b", "c", "d");... - Holger
显示剩余5条评论

2

您可以对原始方法进行少量内联处理,这可以使其更简洁,甚至无需使用流:

    String[] a = fullDomain.split("\\.");
    return String.join(".", Arrays.asList(a)
                                  .subList(Math.max(0, a.length-2), a.length));

如果您真的想使用流,可以使用数组子范围流源:
    String[] a = fullDomain.split("\\.");
    return Arrays.stream(a, Math.max(0, a.length-2), a.length)
                 .collect(Collectors.joining("."));

如果你只有一个流,而且事先不知道它的大小,我建议将元素放入一个ArrayDeque中:
    final int N = 2;
    Stream<String> str = ... ;

    Deque<String> deque = new ArrayDeque<>(N);
    str.forEachOrdered(s -> {
        if (deque.size() == N) deque.removeFirst();
        deque.addLast(s);
    });
    return String.join(".", deque);

当然,这并不像编写一个收集器那样普遍适用,但对于简单情况来说,它可能还可以。

1
如果elements是一个流,您可以编写自定义收集器来仅保留最后的K个元素(可能已经有这样的收集器了):
List<?> lastK = ints.stream().collect(
    Collector.of(
        LinkedList::new,
        (listA, el) -> {
            listA.add(el);
            if (listA.size() > K) {
              listA.remove(0);
            }
        },
        (listA, listB) -> {
            while (listB.size() < K && !listA.isEmpty()) {
              listB.addFirst(listA.removeLast());
            }
            return listB;
        }));

2
你甚至可以使用 ArrayDeque 替代 LinkedList,从而提高累加和合并函数的性能,但需要以 ArrayList::new 作为结束器,如果结果类型必须是 List - Holger

1
如果它是一个索引集合,你可以使用:
IntStream.range(elements.size() - 2, elements.size()).mapToObj(elements::get).forEach(System.out::print);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接