Khi nào tôi nên sử dụng luồng?


99

Tôi vừa gặp một câu hỏi khi sử dụng a Liststream()phương thức của nó . Mặc dù tôi biết cách sử dụng chúng, nhưng tôi không chắc chắn về thời điểm sử dụng chúng.

Ví dụ, tôi có một danh sách, chứa nhiều đường dẫn đến các vị trí khác nhau. Bây giờ, tôi muốn kiểm tra xem một đường dẫn đã cho có chứa bất kỳ đường dẫn nào được chỉ định trong danh sách hay không. Tôi muốn trả lại booleandựa trên việc điều kiện đó có được đáp ứng hay không.

Tất nhiên, đây không phải là một nhiệm vụ khó khăn. Nhưng tôi băn khoăn không biết nên sử dụng luồng hay vòng lặp for (-each).

Danh sách

private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
    "my/path/one",
    "my/path/two"
});

Ví dụ - Luồng

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

Ví dụ - Cho-Mỗi vòng lặp

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if(path.contains(excludePath.toLowerCase())){
            return true;
        }
    }
    return false;
}

Lưu ý rằng paththam số luôn là chữ thường .

Dự đoán đầu tiên của tôi là cách tiếp cận cho từng cách nhanh hơn, vì vòng lặp sẽ trả lại ngay lập tức, nếu điều kiện được đáp ứng. Trong khi luồng sẽ vẫn lặp lại tất cả các mục trong danh sách để hoàn tất quá trình lọc.

Giả định của tôi có đúng không? Nếu vậy, tại sao (hay đúng hơn là khi nào ) tôi lại sử dụng stream()?


11
Luồng biểu cảm và dễ đọc hơn các vòng lặp truyền thống. Về sau, bạn cần phải cẩn thận về bản chất của if-then và các điều kiện, v.v. Biểu thức luồng rất rõ ràng: chuyển đổi tên tệp thành chữ thường, sau đó lọc theo thứ gì đó rồi đếm, thu thập, v.v. Kết quả: rất lặp lại biểu thức của luồng tính toán.
Jean-Baptiste Yunès 27/02/17

12
Không cần thiết new String[]{…}ở đây. Chỉ cần sử dụngArrays.asList("my/path/one", "my/path/two")
Holger

4
Nếu nguồn của bạn là a String[]thì không cần gọi Arrays.asList. Bạn chỉ có thể truyền qua mảng bằng cách sử dụng Arrays.stream(array). Nhân tiện, tôi gặp khó khăn trong việc hiểu isExcludedhoàn toàn mục đích của bài kiểm tra. Có thực sự thú vị không khi một phần tử của EXCLUDE_PATHSđược chứa ở đâu đó trong đường dẫn? Tức là isExcluded("my/path/one/foo/bar/baz")sẽ trở lại true, cũng như isExcluded("foo/bar/baz/my/path/one/")
Holger

3
Tuyệt vời, tôi không biết về Arrays.streamphương pháp, cảm ơn vì đã chỉ ra điều đó. Thật vậy, ví dụ tôi đã đăng có vẻ khá vô dụng đối với bất kỳ ai khác ngoài tôi. Tôi biết về hoạt động của isExcludedphương pháp, nhưng nó thực sự chỉ là thứ tôi cần cho bản thân, do đó, để trả lời câu hỏi của bạn: vâng , thật thú vị vì những lý do tôi không muốn đề cập, vì nó sẽ không phù hợp với phạm vi của câu hỏi ban đầu.
mcuenez 27/02/17

1
Tại sao toLowerCaseáp dụng cho hằng số đã là chữ thường? Nó không nên được áp dụng cho pathđối số?
Sebastian Redl

Câu trả lời:


78

Giả định của bạn là đúng. Việc triển khai luồng của bạn chậm hơn vòng lặp.

Tuy nhiên, việc sử dụng luồng này sẽ nhanh như vòng lặp for:

EXCLUDE_PATHS.stream()  
                               .map(String::toLowerCase)
                               .anyMatch(path::contains);

Điều này lặp lại qua các mục, áp dụng String::toLowerCasevà bộ lọc cho từng mục một và kết thúc ở mục đầu tiên phù hợp.

Cả hai collect()& anyMatch()đều là hoạt động đầu cuối. anyMatch()Tuy nhiên, thoát ở mục tìm thấy đầu tiên, trong khi collect()yêu cầu tất cả các mục phải được xử lý.


2
Tuyệt vời, không biết về findFirst()kết hợp với filter(). Rõ ràng, tôi không biết cách sử dụng stream tốt như tôi nghĩ.
mcuenez 27/02/17

4
Có một số bài báo và bài thuyết trình blog thực sự thú vị trên web về hiệu suất API luồng, mà tôi thấy rất hữu ích để hiểu cách hoạt động của công cụ này. Tôi chắc chắn có thể khuyên bạn chỉ cần nghiên cứu một chút, nếu bạn quan tâm đến điều đó.
Stefan Pries

Sau khi bạn chỉnh sửa, tôi cảm thấy câu trả lời của bạn là câu trả lời nên được chấp nhận, vì bạn cũng đã trả lời câu hỏi của tôi trong phần bình luận của câu trả lời khác. Mặc dù vậy, tôi muốn cung cấp cho @ rvit34 một số tín dụng vì đã đăng mã :-)
mcuenez 27/02/17

34

Quyết định có sử dụng Luồng hay không không nên dựa trên việc xem xét hiệu suất mà dựa trên khả năng đọc. Khi thực sự nói đến hiệu suất, có những cân nhắc khác.

Với .filter(path::contains).collect(Collectors.toList()).size() > 0cách tiếp cận của bạn , bạn đang xử lý tất cả các phần tử và thu thập chúng vào một tệp tạm thời List, trước khi so sánh kích thước, tuy nhiên, điều này hầu như không bao giờ quan trọng đối với một Luồng bao gồm hai phần tử.

Việc sử dụng .map(String::toLowerCase).anyMatch(path::contains)có thể tiết kiệm chu kỳ CPU và bộ nhớ, nếu bạn có số lượng phần tử lớn hơn đáng kể. Tuy nhiên, điều này sẽ chuyển đổi từng Stringthành phần biểu diễn chữ thường của nó, cho đến khi tìm thấy kết quả khớp. Rõ ràng, có một điểm trong việc sử dụng

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

thay thế. Vì vậy, bạn không phải lặp lại chuyển đổi thành chữ thường trong mỗi lần gọi isExcluded. Nếu số phần tử trong EXCLUDE_PATHShoặc độ dài của các chuỗi trở nên thực sự lớn, bạn có thể cân nhắc sử dụng

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

Biên dịch một chuỗi dưới dạng mẫu regex với LITERALcờ, làm cho nó hoạt động giống như các hoạt động chuỗi thông thường, nhưng cho phép công cụ dành một chút thời gian để chuẩn bị, ví dụ như sử dụng thuật toán Boyer Moore, để hiệu quả hơn khi so sánh thực tế.

Tất nhiên, điều này chỉ thành công nếu có đủ các bài kiểm tra tiếp theo để bù lại thời gian chuẩn bị. Việc xác định xem trường hợp này có xảy ra hay không, là một trong những cân nhắc về hiệu suất thực tế, bên cạnh câu hỏi đầu tiên liệu hoạt động này có bao giờ là quan trọng về hiệu suất hay không. Không phải là câu hỏi nên sử dụng Luồng hay forvòng lặp.

Nhân tiện, các ví dụ mã ở trên giữ nguyên logic của mã gốc của bạn, điều này có vẻ đáng ngờ đối với tôi. isExcludedPhương thức của bạn trả về true, nếu đường dẫn được chỉ định chứa bất kỳ phần tử nào trong danh sách, vì vậy nó trả về truefor /some/prefix/to/my/path/one, cũng như my/path/one/and/some/suffixhoặc thậm chí /some/prefix/to/my/path/one/and/some/suffix.

Even dummy/path/onerousđược coi là đáp ứng các tiêu chí vì nó containslà chuỗi my/path/one


Cảm ơn. Về phần cuối cùng của câu trả lời của bạn: nếu câu trả lời của tôi cho nhận xét của bạn không thỏa mãn, hãy coi mã ví dụ của tôi như một người trợ giúp đơn thuần để người khác hiểu những gì tôi đang hỏi - thay vì nó là mã thực tế. Ngoài ra, bạn luôn có thể chỉnh sửa câu hỏi, nếu bạn có một ví dụ tốt hơn trong đầu.
mcuenez 27/02/17

3
Tôi nhận xét của bạn rằng thao tác này là những gì bạn thực sự muốn, vì vậy không cần phải thay đổi nó. Tôi sẽ chỉ giữ phần cuối cùng cho những độc giả trong tương lai, vì vậy họ biết rằng đây không phải là một hoạt động điển hình, ngoài ra, nó đã được thảo luận rồi và không cần bình luận thêm…
Holger

Trên thực tế, các luồng là hoàn hảo để sử dụng cho việc tối ưu hóa bộ nhớ khi lượng bộ nhớ hoạt động đang phá vỡ giới hạn máy chủ
ColacX

21

Vâng. Bạn đúng rồi. Cách tiếp cận luồng của bạn sẽ có một số chi phí. Nhưng bạn có thể sử dụng một cấu trúc như vậy:

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

Lý do chính để sử dụng luồng là chúng làm cho mã của bạn đơn giản và dễ đọc hơn.


3
anyMatchmột lối tắt cho filter(...).findFirst().isPresent()?
mcuenez 27/02/17

6
Vâng, đúng vậy! Điều đó thậm chí còn tốt hơn gợi ý đầu tiên của tôi.
Stefan Pries 27/02/17

8

Mục tiêu của các luồng trong Java là đơn giản hóa sự phức tạp của việc viết mã song song. Nó lấy cảm hứng từ lập trình chức năng. Dòng nối tiếp chỉ để làm cho mã sạch hơn.

Nếu chúng ta muốn có hiệu suất, chúng ta nên sử dụngllelStream, được thiết kế để. Nói chung, một trong những nối tiếp chậm hơn.

Có một bài viết tốt để đọc về , hiệu suất . ForLoopStreamParallelStream

Trong mã của bạn, chúng tôi có thể sử dụng các phương pháp kết thúc để dừng tìm kiếm ở trận đấu đầu tiên. (anyMatch ...)


5
Xin lưu ý rằng đối với các luồng nhỏ và trong một số trường hợp khác, luồng song song có thể chậm hơn do chi phí khởi động. Và nếu bạn có một hoạt động đầu cuối có thứ tự, thay vì một hoạt động song song không có thứ tự, hãy đồng bộ hóa lại ở cuối.
CAD97

0

Như những người khác đã đề cập nhiều điểm tốt, nhưng tôi chỉ muốn đề cập đến đánh giá lười biếng trong đánh giá luồng. Khi chúng tôi map()tạo một luồng các đường dẫn chữ thường, chúng tôi không tạo toàn bộ luồng ngay lập tức, thay vào đó luồng được xây dựng một cách lười biếng , đó là lý do tại sao hiệu suất phải tương đương với vòng lặp for truyền thống. Nó không thực hiện quét toàn bộ map()anyMatch()được thực hiện cùng một lúc. Khi anyMatch()trả về true, nó sẽ bị đoản mạch.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.