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

튜플이 좋을 것입니다. 자바가 가지고 있다면 좋을 것입니다. (그리고 자동 풀기, 멋지게 보이도록) – njzk2

답변

12

인스턴스와 그에 해당하는 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 방법을 사용하는 것입니다 결합 자 (combiner)는 엔트리 병합을 처리합니다. @Holger 지적

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 
} 

, 당신이 더 나은 병합 기능을 사용하여 새 목록의 생성을 방지하여이를 최적화 할 수 있습니다 다음 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); }))'(이것은 기본적으로 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

이러한 경우 튜플을 사용합니다.

public static void main(String [] args) { 
    Map<String, String> sd = Maps.newHashMap(); 
    Stream<Path> stream = Stream.empty(); 
    Map<String, List<Path>> targetDirFilteredAndMapped = stream.parallel() 
     .map(path -> Tuple.tuple(path, md5(path))) 
     .filter(tuple -> sd.containsKey(tuple.right())) 
     .collect(groupingBy(Tuple::right, 
       mapping(Tuple::left, 
       toList()))); 
} 

private static String md5(final Path path) { 
     return "md5"; 
} 

는 Unfortunaltely 자바에는 튜플 없다 (같은를() 스칼라에서) 그래서 같은 클래스 만들었습니다

@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 모두를 저장하는 개인 클래스의 어떤 종류를 만들 수를 터플은 사용하기가 더 빠릅니다.