【问题标题】:Java - Stream - Collect every N elementsJava - Stream - 收集每 N 个元素
【发布时间】:2025-12-11 13:25:04
【问题描述】:

我正在尝试学习 java - 流。我能够做简单的迭代/过滤/映射/收集等。

当我尝试收集每 3 个元素并按此示例中所示打印时,[收集每 3 个元素并打印等等...]

    List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");

    int count=0;
    String append="";
    for(String l: list){
        if(count>2){
            System.out.println(append);
            System.out.println("-------------------");
            append="";
            count=0;
        }
        append = append + l;
        count++;
    }
    System.out.println(append);

输出:

abc
-------------------
def
-------------------
ghi
-------------------
j

我不知道如何使用流来做到这一点。我应该实现自己的收集器来实现这一点吗?

【问题讨论】:

标签: java java-8 java-stream


【解决方案1】:

您实际上可以使用IntStream 来模拟列表的分页。

List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");

int pageSize = 3;

IntStream.range(0, (list.size() + pageSize - 1) / pageSize)
        .mapToObj(i -> list.subList(i * pageSize, Math.min(pageSize * (i + 1), list.size())))
        .forEach(System.out::println);

哪个输出:

[a, b, c]
[d, e, f]
[g, h, i]
[j]

如果你想生成字符串,你可以使用String.join,因为你直接处理的是List&lt;String&gt;

.mapToObj(i -> String.join("", list.subList(i * pageSize, Math.min(pageSize * (i + 1), list.size()))))

【讨论】:

  • 酷...我应该知道子列表
  • 仅供参考: 如果输入是基于数组的List(例如ArrayListArrays.asList()),这才是一个好的答案。对于LinkedList 和其他Collection 对象,其中get(int index) 不是O(1),性能会受到影响。 --- 如果输入是流,也不能使用,这是对问题的一种解释:如何从流中收集每 N 个元素。 --- 不过,+1 有用的答案。
  • @Andreas True,如果源是流,我只是再次在这里链接the solution provided by Holger
【解决方案2】:

如果你的项目中有 Guava,可以使用Iterables.partition 方法:

import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
...

Stream<List<String>> stream = Streams.stream(Iterables.partition(list, 3));

【讨论】:

  • 我不使用它。我可以。会试试
  • 如果潜在的额外 jar 是一个选项并且输入是 Iterable(最常见的情况),则很好的简单解决方案,但如果输入是流则不可用,这是对问题的一种解释:如何从 Stream 中收集每 N 个元素。不过,为有用的答案 +1。
【解决方案3】:

您可以创建自己的Collector。最简单的方法是拨打Collector.of()

由于您的用例需要按顺序处理值,因此这里有一个根本不支持并行处理的实现。

public static Collector<String, List<List<String>>, List<List<String>>> blockCollector(int blockSize) {
    return Collector.of(
            ArrayList<List<String>>::new,
            (list, value) -> {
                List<String> block = (list.isEmpty() ? null : list.get(list.size() - 1));
                if (block == null || block.size() == blockSize)
                    list.add(block = new ArrayList<>(blockSize));
                block.add(value);
            },
            (r1, r2) -> { throw new UnsupportedOperationException("Parallel processing not supported"); }
    );
}

测试

List<String> input = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
List<List<String>> output = input.stream().collect(blockCollector(3));
output.forEach(System.out::println);

输出

[a, b, c]
[d, e, f]
[g, h, i]
[j]

【讨论】:

  • 谢谢。我也会尝试这种方法
【解决方案4】:

我是这样解决的:

    List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
    int groupBy = 3;

    AtomicInteger index = new AtomicInteger(0);         
    Map<Integer, List<String>> groups = list.stream()
        .collect(Collectors.groupingBy(cdm -> index.getAndIncrement()/groupBy));

    System.out.println(groups);

它准备了一个映射,其中行号是键,行上的字符串在键中。

【讨论】:

  • 非常有趣
  • 聪明,但它有副作用。
【解决方案5】:

我认为赌注方法是使用 StreamExTagir Valeev 中的一个惊人的库。该解决方案适合一行))

StreamEx.ofSubLists(list, 3).toList();

【讨论】:

    【解决方案6】:

    最明显的解决方案:

    IntStream.range(0, list.size() / N)
             .map(i -> i * charactersAmount)
             .mapToObj(i -> list.subList(i, i + charactersAmount)
             .collect(Collectors.toWhateverYouWant());
    

    第一行 - 您将获得从 0 到结果行数的整数流。从您的示例中,list.size() / N 等于 4,因此流将为 0-1-2-3。

    第二行 - 此流将映射到按字符缩放的数量一,在您的情况下为 3 - 0-3-6-9。

    第三行将从您的初始列表中删除子列表。

    最后一行只是将生成的流视为集合

    【讨论】:

      最近更新 更多