使用Java 8流的map函数执行长时间运行的任务

时间:2019-10-28 16:30:56

标签: java dictionary java-stream long-running-processes

我有一个方法,该方法采用一个URL列表(远程文件)作为应下载的参数。该方法返回其他类型的列表(称为附件),该列表实际上在内部包含java File-type的属性。 对于这种情况,我使用Java Stream API遍历URL,并在“地图”功能内开始下载,该功能实际上返回了Attachment实例。

现在我的问题是:我是否在滥用Java Stream API来处理其不想要的事情?像将长期运行任务放入其中吗?我应该对输入数据做些小操作吗?

我现在唯一看到的缺点是它很难测试。

private List<Attachment> download(List<URL> attachments) {
        return attachments.stream().map(attachmentUrl -> {
            try {
                Attachment attachment = new Attachment();
                File attachmentFile = new File(getFilename(attachment.getAttachmentId(), attachmentUrl));
                FileUtils.copyURLToFile(
                        attachmentUrl,
                        attachmentFile,
                        CONNECT_TIMEOUT,
                        READ_TIMEOUT);
                attachment.setAttachmentFile(attachmentFile);
                return attachment;
            } catch (IOException e) {
                e.printStackTrace();
                LOGGER.error(e.getLocalizedMessage());
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

2 个答案:

答案 0 :(得分:1)

流是一种非常优雅的工具,可以以功能性的编程方式处理数据,相同的输入将导致相同的输出而没有副作用,这将使您的代码更不易出错且更具可读性。因此,无论输入大小,都不会滥用。如果您期望处理大量数据,则可以使用并行流。但是,您的实现可能需要进行一些清理,不要将所有业务逻辑都委派给单个映射操作,而是使其更加精细,并将逻辑扩展到多个映射器中,您可以像任何变量Function<URL, File> urlToFileMapper = url -> {...}一样声明映射器,并且将映射器插入流attachments.stream().map(urlToFileMapper).map(anotherDeclaredMapper)...

答案 1 :(得分:1)

我认为考虑map和其他功能结构(例如filterreduce等)可能会有所帮助,而不仅仅是考虑功能,而是考虑语法stream().map()是一种语法,其功能等效于for循环。问“我是因为使用它来执行此语法吗?”这样就没有什么意义了:for循环不在乎它们在每次迭代中运行多长时间,map也不在乎。它与所应用的 operation 无关,因此唯一的问题是您是否适当地使用了语法,即循环遍历一个集合,将映射从一个对象映射到另一个对象。

在这种情况下,map是语法,您所期望的操作完全正确。但是,您的实现可能会被清除。

attachmentUrl -> {
    try {
        Attachment attachment = new Attachment();
        File attachmentFile = new File(getFilename(attachment.getAttachmentId(), attachmentUrl));
        FileUtils.copyURLToFile(
                attachmentUrl,
                attachmentFile,
                CONNECT_TIMEOUT,
                READ_TIMEOUT);
        attachment.setAttachmentFile(attachmentFile);
        return attachment;
    } catch (IOException e) {
        e.printStackTrace();
        LOGGER.error(e.getLocalizedMessage());
    }
    return null;
}

对于内联map lambda来说有点大。通常,对于需要花括号(即占用多行)的map lambda,我总是持怀疑态度,尽管并非总是不赞成。我建议将这个lambda重构为一个命名函数,可能还有几个,它们是嵌套的(map(this::A),其中A然后调用B)或由流操作串行使用{ {1}}。


[EDIT:]关于并行化map(this::A).map(this::B):请记住,作为此方法的一部分,您所做的不仅仅是CPU处理-您似乎正在做网络IO 文件IO。如果并行执行,则不仅要并行化CPU使用率,还要并行化网络和磁盘使用率。如果网络磁盘是主要因素,而不是CPU是主要因素,那么并行化将使您受益匪浅,并使情况变得更糟。通常,更多线程!=更快的网络或磁盘读/写。您可能会发现this question on parallel IO有用。