使用 Java Streams 组装列表

Posted

技术标签:

【中文标题】使用 Java Streams 组装列表【英文标题】:Use Java Streams to assemble a list 【发布时间】:2022-01-22 05:33:18 【问题描述】:

我有一个名为“文件”的实体,具有以下属性:

public class File 
    private int id;
    private String proposal;
    private String hash;
    private String path;
    private LocalDateTime createdAt;
    private LocalDateTime finishedAt;
    private int size;
    private boolean processed;

我需要组织一个队列,将哪些文件上传到特定服务。对于每次提交,我可以发送尽可能多的文件,只要它们的大小总和不超过 100mb。此外,我必须尊重每个文件允许的最大日期(在这种情况下涉及finishedAt 属性)。

我已经设法对我的列表进行排序。换句话说,我能够确定首先上传哪些文件:

files.sort(Comparator.comparing(File::getFinishedAt));
files.stream().forEach(file -> System.out.println(file.getId()));

现在我想组装一个数组,其中包含每次提交时将排序的文件列表。类似的东西:

[[1, 4, 7], [8, 2], [6, 3], [5]]

上述数组的每个“子集”都与上传有关。上面是我的文件的 ID。所以我希望我的程序返回一个数组数组,其中每个集合都包含尽可能多的文件(只要它们的总和不超过 100mb)。由于每条记录都有截止日期 (finishedAt),因此它们也需要按顺序排列。

最合适的方法是什么?我试图理解 Java 中的 Streams,但我能做的最多就是根据日期对列表进行排序。使用 Streams 我可以达到我展示的结果吗?实现这些目标最合适的方法是什么?

非常感谢!

【问题讨论】:

不清楚你想做什么。 [[1, 4, 7], [8, 2], [6, 3], [5]]File[][](或 List<List<File>>)类型吗?这是您的程序的结果,还是您的程序的输入?各个组(如1, 4, 7)代表什么? “尊重提交的最长时间”是什么意思?一些示例输入及其相应的输出可能会阐明您的目标。 哦。 @VGR,我真的没有足够清楚地说明这些问题。我将编辑我的问题,但该数组是一个处理响应,它向我显示必须在每批中上传的文件的 ID。换句话说:从包含所有注册文件的列表中,我的程序必须设置上传顺序。每次上传可以有多个文件,但它们的总和不能超过 100mb。此外,每个文件都有一个最大上传日期。我的目的是根据这些要求(finishedAt 和大小)确定每批中将发送哪些文件。 在我看来(因此不是官方答案),你想要做的不是一个好的流用例。到目前为止您所拥有的部分很好,但我会将分块部分编写为您当前拥有的列表的循环,构建列表直到它们“满”,然后将每个列表添加到“列表列表” .即使您确实使用流进行了此操作,代码也可能不太容易理解,从而导致维护头痛。 我认为你是对的,@GreyBeardedGeek 关键是我对 Java 不是很流利,所以我认为一切都应该以“Java 8”的方式完成。考虑到您所说的,我会看看是否可以更接近解决方案。如果不是要求太多,你能给我举个例子或者给我一些我可以创建这个数组数组的内容吗? 是否保证每个文件的大小在100MB以下?否则,可能需要额外拆分文件以适应大小限制。 【参考方案1】:

Stream API 有其自身的局限性,可能不适用于给定任务中发生的有状态操作:此处应收集子列表(或需要增加适当的索引)直到满足大小限制,并且 那么计算应该从最后处理的索引计算。也就是说,有两个状态参数:总和和定义子列表的第一/最后索引对。

因此,可能会建议使用旧的基于循环的良好解决方案。

更新

添加了返回 int[][] 数组和 ID。

static int[][] groupFiles(List<File> files) 
    files.sort(Comparator.comparing(File::getFinishedAt));
    
    List<int[]> result = new ArrayList<>();
    
    for (int i = 0, n = files.size(); i < n; i++) 
        int sum = 0;
        int j = i;
        for (; j < n && sum + files.get(j).size <= 100; sum += files.get(j++).size);
    
        System.out.println("total size=" + sum + "; sublist: [" + i + ", " + j + "]");
        
        List<File> sublist = files.subList(i, j);
        
        sublist.forEach(s -> System.out.println("\t" + s));
        i = --j;
        
        sendFiles(sublist);
        result.add(sublist.stream().mapToInt(File::getId).toArray());
    
    return result.toArray(new int[0][]);

测试:

List<File> files = Arrays.asList(
    new File(1, "f1", LocalDateTime.of(2021, 12, 20, 20, 00), 55),
    new File(2, "f2", LocalDateTime.of(2021, 12, 20, 19, 45), 20),
    new File(3, "f3", LocalDateTime.of(2021, 12, 20, 19, 30), 40),
    new File(4, "f4", LocalDateTime.of(2021, 12, 20, 19, 50), 45),
    new File(5, "f5", LocalDateTime.of(2021, 12, 20, 20, 10), 35)
);
        
System.out.println(Arrays.deepToString(groupFiles(files)));

输出:

total size=60; sublist: [0, 2]
    File: id=3; path=f3; finishedAt=2021-12-20T19:30; size=40
    File: id=2; path=f2; finishedAt=2021-12-20T19:45; size=20
total size=100; sublist: [2, 4]
    File: id=4; path=f4; finishedAt=2021-12-20T19:50; size=45
    File: id=1; path=f1; finishedAt=2021-12-20T20:00; size=55
total size=35; sublist: [4, 5]
    File: id=5; path=f5; finishedAt=2021-12-20T20:10; size=35
[[3, 2], [4, 1], [5]]

【讨论】:

天哪!几天来我一直在绞尽脑汁想做出这个选择!非常感谢!!!现在我将继续尝试捕获“id”以返回我在问题中提到的格式(在本例中为数组数组)。所以,不想过多滥用它(因为你对我帮助很大),你能给我一个方向吗? @SpammingOff,你可以查看更新 非常感谢!!!你在这个解决方案上帮了我很多忙!我正在尝试更复杂的路径(而且一点也不优雅)。我将逐行阅读以真正学习。我敢肯定,我将来需要开发类似的东西,而您的帮助至关重要。再次感谢您!

以上是关于使用 Java Streams 组装列表的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 8 Streams 从列表中仅获取所需的对象

如果使用我的服务使用Java 8 lambda / streams列表不为空,如何从列表中删除每个元素

java8的Streams

Java 8 Streams 减少删除重复项,保留最新条目

Java 8 Streams:根据不同的属性多次映射同一个对象

Java8新特性Streams常用API案例详解