使用流按条件填充地图 - Java 8

4

我试图将这段(简化的)代码转换为使用Java-8流的形式:

Map<String, String> files = new ConcurrentHashMap<String, String>();

while(((line = reader.readLine()) != null) {
      if(content != null)
        files.put("not null"+line, "not null"+line);
      else
        files.put("its null"+line, "its null"+line);
    }
reader.close();

以下是我尝试过的方法:

files = reader.lines().parallel().collect((content != null)?
                (Collectors.toConcurrentMap(line->"notnull"+line, line->line+"notnull")) :                                              
                (Collectors.toConcurrentMap(line->line+"null", line->line+"null")));

但是,在IntelliJ上,所有关于line->line+"..."的内容都会出现“循环推理”信息。什么是循环推理?这个逻辑有错误吗?
我在Stack Overflow上注意到了一些类似的问题。但它们建议使用接口(Map)而不是其实现。但是,这里的files被声明为Map
更新:增加更多背景信息,content是一个包含目录名称的字符串。 files是一个包含多个文件路径的映射。哪些文件路径需要进入files映射取决于是否填充了content目录名称。

1
代码示例有点不清晰。(1)“content”变量是什么?(2)您是在读取单个文件的行,但将它们放入名为“files”的映射中吗? - Jan X Marek
1
  1. content 是一个字符串,保存着目录的名称。
  2. 是的,我正在从文本文件中读取行(基本上包含一些部分标记),并将其附加到文件路径中,这些路径将一起进入文件映射。
- A.R.K.S
3个回答

8
另一种解决方法是引入中间变量来收集器:
Collector<String, ?, ConcurrentMap<String, String>> collector = (content != null) ?
        (Collectors.toConcurrentMap(line->"notnull"+line, line->line+"notnull")) :
        (Collectors.toConcurrentMap(line->line+"null", line->line+"null"));
Map<String, String> files = reader.lines().parallel().collect(collector);       

这种解决方案(与@JanXMarek提出的解决方案不同)不会分配中间数组,并且不会为每个输入行检查content

循环推断是类型推断过程中的一种情况,当需要确定内部子表达式的类型时,必须确定外部子表达式的类型,但是在不知道内部子表达式的类型的情况下无法确定外部子表达式的类型。在Java-8中,类型推断可以推断出Stream<String>.collect(Collectors.toConcurrentMap(line->line+"null", line->line+"null"))的Collector类型为Collector<String, ?, ConcurrentMap<String, String>>。通常情况下,当子表达式类型(这里我们谈论的是toConcurrentMap(...) 子表达式)无法明确确定时,如果外部上下文是方法调用、强制转换或赋值,则可以使用外部上下文进行缩减。然而,在这里,外部上下文是?: 运算符,它有自己复杂的类型推断规则,因此这变得太多了,您应该在某个地方帮助类型推断系统指定显式类型。


我认为这个解决方案更易读和高效,因为你没有动态分配太多的内存。 - Debosmit Ray
我喜欢这个解决方案,但是我会将三元运算符移到keyMappervalueMapper函数定义中,并在Collectors.toConcurrentMap中使用它们。 - fps

4
你可以像这样完成它。
reader.lines().parallel()
    .map(line -> content == null ?
            new String[]{"notnull"+line, line+"notnull"} :
            new String[]{line+"null", line+"null"})
    .collect(Collectors.toConcurrentMap(pair -> pair[0], pair -> pair[1]));

首先,您需要将该行映射到存储在数组中的(键,值)对(或某种Pair对象)中,然后在收集器中再次将其拆分为键和值。


谢谢@Jan。它怎么知道pair是一个字符串数组?我应该在toConcurrentMap()内部声明它吗?我认为.map()应该将字符串数组传递到pair变量中,但我得到了一个错误,说它未定义。 - A.R.K.S
1
抱歉,我刚刚修复了代码中的一个拼写错误。但是你不需要在任何地方声明类型。Java编译器会从map()操作的结果中推断出它。你只需复制粘贴我的代码,它应该可以工作(只要“reader”和“content”变量已定义)。如果不能,请告诉我你得到了什么错误? - Jan X Marek
我还是很好奇什么是循环推理。能有人解释一下吗? - A.R.K.S

1

仅作为一个旁注,我怀疑在这种情况下.parallel()没有任何用处。如果您正在使用标准的Java API读取文件,则底层迭代器仍将按顺序读取文件。唯一并行执行的是转换行。出于好奇心,我在我的电脑上尝试了一下,没有使用.parallel()时速度提高了约10%。

如果处理速度比流输入读取速度慢一个数量级,那么并行化是有意义的,但在这里不是这种情况。


嗨Jan,只是为了明确一下,您是否尝试过parallel() + toConcurrentMap()组合与非并行+toMap()组合?您发现非并行+toMap()的组合速度更快? - A.R.K.S
我进行了并行和非并行的ConcurrentMap测试,输入文件大约有10万行。可能在不同的硬件或操作系统上表现不同。也许在这种情况下,在您的系统上使用并行处理会获得10%的性能提升。我的观点是.parallel()不是免费的午餐。即使您拥有8核处理器,在93.73547%的真实情况下,它的表现也不会比非并行流更好,因为Ahmdal定律总是潜伏在某个地方。 - Jan X Marek

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