Java 8 Stream通过相同的昂贵方法调用进行过滤和分组

Gib*_*tar 14 java java-8 java-stream

我正在寻找一种以Stream干净的方式优化处理的方法.

我有类似的东西:

try (Stream<Path> stream = Files.list(targetDir)) {
    Map<String, List<Path>> targetDirFilteredAndMapped = stream.parallel()                                                                                                
        .filter(path -> sd.containsKey(md5(path)))                                                                                                                    
        .collect(Collectors.groupingBy(path -> md5(path)));
} catch (IOException ioe) { // manage exception }
Run Code Online (Sandbox Code Playgroud)

由于该md5功能非常昂贵,我想知道是否有办法每个文件只调用一次.

有什么建议?

Era*_*ran 12

您可以创建一些PathWrapper包含Path实例及其对应的对象md5(path).

public class PathWrapper
{
    Path path;
    String md5; // not sure if it's a String
    public PathWrapper(Path path) {
        this.path = path;
        this.md5 = md5(path);
    }
    public Path getPath() {return path;}
    public String getMD5() {return md5;}
}
Run Code Online (Sandbox Code Playgroud)

然后将您的信息流映射到Stream<PathWrapper>:

try (Stream<Path> stream = Files.list(targetDir)) {
    Map<String, List<Path>> targetDirFilteredAndMapped =
        stream.parallel() 
              .map(PathWrapper::new)
              .filter(path -> sd.containsKey(path.getMD5()))                                                                                                                    
              .collect(Collectors.groupingBy(PathWrapper::getMD5,
                                             Collectors.mapping(PathWrapper::getPath,
                                                                Collectors.toList())));
} catch (IOException ioe) { /* manage exception */ }
Run Code Online (Sandbox Code Playgroud)

  • 您甚至可以使用`AbstractMap.SimpleImmutableEntry`而不是自己的类 (3认同)

Hol*_*ger 7

如果md5操作真正主导了性能,您可以考虑在此处取消过滤,然后删除不匹配的组:

try(Stream<Path> stream = Files.list(targetDir)) {
    Map<String, List<Path>> targetDirFilteredAndMapped = stream.parallel()
        .collect(Collectors.groupingBy(p -> md5(p), HashMap::new, Collectors.toList()));
    targetDirFilteredAndMapped.keySet().retainAll(sd.keySet());
} catch (IOException ioe) { 
    // manage exception
}
Run Code Online (Sandbox Code Playgroud)

当然,这暂时需要更多内存.如果这是一个问题,使用更复杂的解决方案,如其他答案所示,是不可避免的.


Ale*_* C. 5

创建专用类的另一种collect方法是直接使用该方法,您将负责md5在累加器中进行计算以及组合器将在何处合并条目.

try (Stream<Path> stream = Files.list(targetDir)) {
    Map<String, List<Path>> targetDirFilteredAndMapped =
        stream.parallel()
              .collect(HashMap::new,
                       (m, p) -> {
                           String res = md5(p);
                           if(sd.containsKey(res)) {
                               m.computeIfAbsent(res, k -> new ArrayList<>()).add(p);
                           }
                        },
                        (m1, m2) -> m2.forEach((k, v) -> m1.computeIfAbsent(k, k2 -> new ArrayList<>()).addAll(v)));
} catch (IOException ioe) { 
    // manage exception
}
Run Code Online (Sandbox Code Playgroud)

正如@Holger指出的那样,您可以通过避免使用更好的合并函数创建新列表来优化它:

(m1, m2) -> m2.forEach((k,v) -> m1.merge(k, v, (l1,l2) -> { l1.addAll(l2); return l1; })) 
Run Code Online (Sandbox Code Playgroud)