2016-09-29 1 views
14

Streamの処理をきれいに最適化する方法を探しています。 md5機能は非常に高価であるため、ファイルごとに一度だけ、それを呼び出すための方法がありますならば、私は思っていた 同じ高価なメソッド呼び出しによるJava 8ストリームのフィルタリングとグループ化

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 } 

は、私はそのようなものを持っています。

提案がありますか?

+0

タプルは、Javaがそれらを持っていれば、それにはいいでしょう。 – njzk2

答えて

12

Pathインスタンスとそれに対応するmd5(path)を含むPathWrapperオブジェクトを作成できます。

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;} 
} 

その後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 */ } 
+3

独自のクラスの代わりに 'AbstractMap.SimpleImmutableEntry'を使用することもできます。 –

+1

@ArneBurmeister良いアイデアは、私はそのクラスについて知りませんでした。そのクラス名でメソッド参照がかなり長くなるかもしれません:) – Eran

+1

または専用のラッパークラスを作成したくない場合は、 'javafx.util.Pair'をラッパークラスとして使用してください。 –

5

専用のクラスを作成する別の方法は、あなたがアキュムレータにmd5計算を行うの世話をします場合は、直接collect方法を使用することですコンバイナがエントリのマージを処理する場所。

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 
} 

@Holgerが指摘したように、あなたがより良いマージ機能を使用して新しいリストの作成を回避することで、これを最適化することができます:md5操作は本当にパフォーマンスを支配している場合は、あなたが考えるかもしれ

(m1, m2) -> m2.forEach((k,v) -> m1.merge(k, v, (l1,l2) -> { l1.addAll(l2); return l1; })) 
+0

'HashMap :: new'がどのように並列ストリームを扱うかわかりません...マージがシングルスレッドであるという保証はありますか? – GPI

+1

@ GPIこれはサプライヤの機能です。そのため、各スレッドは独自の空のマップで作業を開始します。 –

+1

マージ関数は、常に 'addAll'を使ってリソースを浪費し、最初のマップにリストがないときに新しいリストを構築します。より良い関数は、(m1、m2)→m2.forEach((k、v)→m1.merge(k、v、(l1、l2)→l1.addAll(l2); return l1; })) '(これは基本的に' groupingBy'組み込み関数が使うものです)。 – Holger

7

をここでフィルタリングをオフにして、その後一致しないグループを削除してください:

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 
} 

これはもちろん、一時的にm鉱石の記憶。これが懸念される場合は、他の回答に示されているように、より複雑な解決策を使用することは避けられません。

0

私はそのような場合にタプルを使用します。

​​3210

UnfortunaltelyがJavaで何のタプルは(Scalaでは()のように)ませんので、私はそのようなクラスを作成しました:

@ToString 
@EqualsAndHashCode 
public class Tuple<L, R> { 
    public static <L, R> Tuple<L, R> tuple(L left, R right) { 
     return new Tuple<>(left, right); 
    } 

    private final L left; 
    private final R right; 

    private Tuple(L left, R right) { 
     this.left = left; 
     this.right = right; 
    } 

    public L left() { 
     return left; 
    } 

    public R right() { 
     return right; 
    } 
} 

ます。また、パスとMD5の両方を保存するプライベートクラスのいくつかの種類を作成することができますタプルは使用するほうが速いです。

関連する問題