我正在编写一个程序来从源代码下载历史报价。源提供每天需要解析和处理的文件。该程序使用CompletableFuture
使用不同的阶段并行下载多个文件。第一阶段是使用HttpClient
进行Http调用并获得响应。
getHttpResponse()
方法返回CloseableHttpResponse
对象。我还想返回一个为此http请求发出的URL。最简单的方法是让一个包装器对象具有这两个字段,但我觉得让一个类只包含这两个字段太多了。我可以用CompletableFuture
或Streams来实现这个目标吗?
filesToDownload.stream()
.map(url -> CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor) )
.map(httpResponseFuture -> httpResponseFuture.thenAccept(t -> processHttpResponse(t)))
.count();
答案 0 :(得分:1)
目前尚不清楚为什么要不惜一切代价引入Stream API。将CompletableFuture
用法拆分为两个map
操作会导致问题,否则将无法解决。除此之外,使用map
副作用是滥用Stream API。如果filesToDownload
是具有已知大小的Stream源(几乎每个Collection),这可能在Java 9中完全破坏。然后,count()
将返回已知大小,而不处理map
操作的功能......
如果您想将URL
和CloseableHttpResponse
传递给processHttpResponse
,您可以轻松完成:
filesToDownload.forEach(url ->
CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor)
.thenAccept( t -> processHttpResponse(t, url))
);
即便如果您使用Stream API收集结果,也没有理由将CompletableFuture
拆分为多个map
操作:
List<…> result = filesToDownload.stream()
.map(url -> CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor)
.thenApply( t -> processHttpResponse(t, url)) )
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
请注意,在等待第二个Stream操作中的任何结果之前,这会将CompletableFuture
收集到List
中。这比使用并行Stream操作更可取,因为它确保在开始等待之前已经提交了所有异步操作。
使用单个Stream管道意味着在提交第二个作业之前等待完成第一个作业,并且使用并行Stream只会减少该问题而不是解决问题。它将取决于Stream实现的执行策略(默认的Fork / Join池),它会干扰指定执行程序的实际策略。例如,如果指定的执行程序应该使用比CPU核心更多的线程,则Stream仍然会一次提交与核心一样多的作业 - 如果默认的Fork / Join池中有其他作业,则更少。 / p>
相反,上述解决方案的行为将完全由指定执行程序的执行策略控制。