Когда я должен использовать потоки?


Я просто наткнулся на вопрос при использовании List и stream() метод. Пока я знаю как чтобы использовать их, я не совсем уверен о , когда для их использования.

например, у меня есть список, содержащий различные пути в разных местах. Теперь я хотел бы проверить, содержит ли один заданный путь любой из путей, указанных в списке. Я хотел бы вернуть a boolean в зависимости от того, было ли выполнено условие.

это конечно, это не сложная задача, как таковой. Но мне интересно, следует ли использовать потоки или цикл for(-each).

Список

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

Example-Stream

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

Пример - Для Каждого Цикла

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

Примечание что

4 67

4 ответа:

ваше предположение верно. Реализация потока выполняется медленнее, чем цикл for.

это использование потока должно быть так же быстро, как для цикла, хотя:

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

это повторяется через элементы, применяя String::toLowerCase и фильтр к элементам один за другим и оканчивается на первом пункте, который соответствует.

и collect() & anyMatch() - Это операции, терминал. anyMatch() выходит на первый найденный элемент, хотя, в то время как collect() требует все элементы для обработки.

решение о том, использовать потоки или нет, должно определяться не соображениями производительности, а скорее удобочитаемостью. Когда дело действительно доходит до производительности, есть и другие соображения.

С .filter(path::contains).collect(Collectors.toList()).size() > 0 подход, вы обрабатываете все элементы и собираете их во временное List, прежде чем сравнивать размер, все же, это вряд ли когда-либо имеет значение для потока, состоящего из двух элементов.

используя .map(String::toLowerCase).anyMatch(path::contains) можете сохранить циклов процессора и памяти, если у вас есть существенно большее количество элементов. Тем не менее, это преобразует каждый String к его строчному представлению, пока не будет найдено совпадение. Очевидно, что есть смысл в использовании

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);
}
. Поэтому вам не нужно повторять преобразование в lowcase при каждом вызове isExcluded. Если количество элементов в EXCLUDE_PATHS или длины строк становится действительно большим, вы можете рассмотреть возможность использования
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));
}

компиляция строки в виде шаблона регулярного выражения с помощью LITERAL флаг, заставляет его вести себя так же, как обычные строковые операции, но позволяет движку потратить некоторое время на подготовку, например, используя алгоритм Бойера Мура, чтобы быть более эффективным, когда дело доходит до фактического сравнения.

конечно, это окупается только в том случае, если есть достаточно последующих тестов, чтобы компенсировать время, затраченное на подготовку. Определение того, будет ли это так, является одним из фактических соображений производительности, помимо первого вопроса о том, будет ли эта операция всегда будет критическая производительность на всех. Не вопрос, Использовать ли потоки или for петли.

кстати, приведенные выше примеры кода сохраняют логику вашего исходного кода, что кажется мне сомнительным. Ваш isExcluded возвращает true, если указанный путь содержит любой из элементов в списке, поэтому он возвращает true на /some/prefix/to/my/path/one, а также my/path/one/and/some/suffix или даже /some/prefix/to/my/path/one/and/some/suffix.

даже dummy/path/onerous считается удовлетворяющим критериям, как это contains в строка my/path/one...

да. Вы правы. Ваш подход к потоку будет иметь некоторые накладные расходы. Но вы можете использовать такую конструкцию:

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

основная причина использования потоков заключается в том, что они делают ваш код более простым и легким для чтения.

цель потоков в Java заключается в упрощении сложности написания параллельного кода. Он вдохновлен функциональным программированием. Последовательный поток-это просто сделать код чище.

Если мы хотим производительности, мы должны использовать parallelStream,который был разработан. Серийный, В общем, медленнее.

есть хорошая статья, чтобы прочитать о ForLoop,Stream и ParallelStream производительность.

в коде мы можем использовать методы прекращения чтобы остановить поиск на первом матче. (anyMatch...)