2016-09-29 44 views
14

Tôi đang tìm cách tối ưu hóa việc xử lý Stream một cách rõ ràng.Java 8 Lọc và nhóm luồng bằng cùng một phương thức đắt tiền gọi

Tôi có một cái gì đó như thế:

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 } 

và kể từ khi md5 chức năng là khá đắt tiền, tôi đã tự hỏi nếu có một cách để gọi nó một lần duy nhất cho mỗi tập tin.

Mọi đề xuất?

+0

bộ dữ liệu sẽ tốt cho điều đó, nếu java có chúng. (và tự động giải nén, để làm cho nó trông đẹp) – njzk2

Trả lời

12

Bạn có thể tạo một số đối tượng PathWrapper có chứa phiên bản Path và số md5(path) tương ứng của nó.

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

Sau đó, bản đồ luồng của mình để 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

Bạn thậm chí có thể sử dụng 'AbstractMap.SimpleImmutableEntry' thay vì một lớp riêng –

+1

@ArneBurmeister Ý tưởng hay, tôi không biết về lớp đó. Mặc dù các tham chiếu phương thức sẽ nhận được khá lâu với tên lớp đó: – Eran

+1

hoặc sử dụng 'javafx.util.Pair' làm lớp trình bao bọc nếu chúng ta không muốn tạo một lớp trình bao bọc chuyên dụng –

5

Một sự thay thế của việc tạo ra một lớp học dành riêng là sử dụng phương pháp collect trực tiếp, nơi bạn sẽ chăm sóc làm việc tính toán md5 trong accumulator và nơi bộ kết hợp sẽ xử lý việc sáp nhập các mục nhập.

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 
} 

Như @Holger chỉ ra, bạn có thể tối ưu hóa này bằng cách tránh việc tạo ra một danh sách mới bằng một chức năng kết hợp tốt hơn:

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

Không chắc chắn làm thế nào 'HashMap :: new' sẽ đối phó với một dòng song song ... Có lẽ có một số loại bảo đảm rằng việc sáp nhập là đơn luồng? – GPI

+1

@GPI Đó là chức năng của nhà cung cấp, do đó, mỗi chủ đề sẽ bắt đầu với bản đồ trống của riêng nó để thực hiện công việc của mình. –

+1

Hàm kết hợp sẽ lãng phí tài nguyên bằng cách luôn sử dụng 'addAll', xây dựng một danh sách mới khi không có danh sách trong bản đồ đầu tiên. Một hàm tốt hơn sẽ là '(m1, m2) -> m2.forEach ((k, v) -> m1.merge (k, v, (l1, l2) -> {l1.addAll (l2); trả về l1; })) '(Điều này về cơ bản là những gì bộ dựng sẵn' groupingBy' sử dụng). – Holger

7

Nếu hoạt động md5 đang thực sự thống trị hiệu suất, bạn có thể xem xét rời khỏi bộ lọc ở đây và chỉ cần xóa các nhóm không khớp sau đó:

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 
} 

Điều này, tất nhiên, tạm thời yêu cầu m bộ nhớ quặng. Nếu đây là một mối quan tâm, sử dụng một giải pháp phức tạp hơn, như được hiển thị trong các câu trả lời khác, là không thể tránh khỏi.

0

Tôi sử dụng bộ dữ liệu cho các trường hợp như vậy.

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 không có tuple trong java (như() trong scala) vì vậy tôi đã tạo lớp như:

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

Bạn cũng có thể tạo ra một số loại lớp tin mà các cửa hàng cả con đường và md5 nhưng tuples chỉ nhanh hơn để sử dụng.

Các vấn đề liên quan