Я просто столкнулся с вопросом при использовании a List
и его stream()
метода. Хотя я знаю, как их использовать, я не совсем уверен, когда их использовать.
Например, у меня есть список, содержащий разные пути в разные места. Теперь я хотел бы проверить, содержит ли один заданный путь какой-либо из путей, указанных в списке. Я хотел бы вернуть в boolean
зависимости от того, было ли выполнено условие.
Это, конечно, не сложная задача. Но мне интересно, следует ли мне использовать потоки или цикл for (-each).
Список
private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
"my/path/one",
"my/path/two"
});
Пример - поток
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;
}
Обратите внимание, что path
параметр всегда в нижнем регистре .
Мое первое предположение заключается в том, что подход for-each быстрее, потому что цикл вернется немедленно, если условие будет выполнено. В то время как поток по-прежнему будет перебирать все записи списка, чтобы завершить фильтрацию.
Верно ли мое предположение? Если да, то почему (или, скорее, когда ) я должен использовать это stream()
?
источник
new String[]{…}
. Просто используйтеArrays.asList("my/path/one", "my/path/two")
String[]
, звонить не нужноArrays.asList
. Вы можете просто передавать поток через массив, используяArrays.stream(array)
. Кстати, мне вообще сложно понять цельisExcluded
теста. Неужели действительно интересно, содержится ли элементEXCLUDE_PATHS
буквально где-то в пути? Т.е.isExcluded("my/path/one/foo/bar/baz")
вернетсяtrue
, как иisExcluded("foo/bar/baz/my/path/one/")
…Arrays.stream
методе, спасибо, что указали на это. Действительно, опубликованный мною пример кажется совершенно бесполезным ни для кого, кроме меня. Я знаю о поведении этогоisExcluded
метода, но на самом деле это просто то, что мне нужно для меня, поэтому, чтобы ответить на ваш вопрос: да , это интересно по причинам, о которых я не хотел бы упоминать, поскольку он не вписывается в область исходного вопроса.toLowerCase
применяется к константе, которая уже является строчной? Разве это не должно применяться кpath
аргументу?Ответы:
Ваше предположение верно. Реализация вашего потока медленнее, чем цикл for.
Однако использование этого потока должно быть таким же быстрым, как и цикл for:
Это выполняет итерацию по элементам, применяя
String::toLowerCase
и фильтр к элементам один за другим и завершая на первом совпадающем элементе .Оба
collect()
&anyMatch()
являются терминальными операциями.anyMatch()
однако завершает работу по первому найденному элементу, аcollect()
требует обработки всех элементов.источник
findFirst()
в сочетании сfilter()
. Видимо, я не умею использовать потоки так хорошо, как думал.Решение о том, использовать ли Streams или нет, должно определяться не соображениями производительности, а скорее удобочитаемостью. Когда дело доходит до производительности, есть и другие соображения.
С вашим
.filter(path::contains).collect(Collectors.toList()).size() > 0
подходом вы обрабатываете все элементы и собираете их во временныйList
, прежде чем сравнивать размер, тем не менее, это почти никогда не имеет значения для Stream, состоящего из двух элементов.Использование
.map(String::toLowerCase).anyMatch(path::contains)
может сэкономить циклы процессора и память, если у вас значительно большее количество элементов. Тем не менее, это преобразует каждоеString
в его представление в нижнем регистре, пока не будет найдено совпадение. Очевидно, есть смысл использоватьвместо. Таким образом, вам не нужно повторять преобразование в нижний регистр при каждом вызове
isExcluded
. Если количество элементовEXCLUDE_PATHS
или длина строк становится действительно большой, вы можете рассмотреть возможность использованияКомпиляция строки как шаблона регулярного выражения с
LITERAL
флагом заставляет ее вести себя так же, как обычные строковые операции, но позволяет механизму потратить некоторое время на подготовку, например, используя алгоритм Бойера Мура, чтобы быть более эффективным, когда дело доходит до фактического сравнения.Конечно, это окупается только в том случае, если будет достаточно последующих тестов, чтобы компенсировать время, потраченное на подготовку. Определение того, будет ли это так, является одним из фактических соображений производительности, помимо первого вопроса, будет ли эта операция вообще когда-либо критичной для производительности. Вопрос не в том, использовать ли Streams или
for
циклы.Кстати, приведенные выше примеры кода сохраняют логику исходного кода, что мне кажется сомнительным. Ваш
isExcluded
метод возвращаетtrue
, если указанный путь содержит любой из элементов в списке, поэтому он возвращаетсяtrue
для/some/prefix/to/my/path/one
, asmy/path/one/and/some/suffix
или even/some/prefix/to/my/path/one/and/some/suffix
.Даже
dummy/path/onerous
считается отвечающим критериям, поскольку этоcontains
строкаmy/path/one
…источник
Да. Ты прав. Ваш потоковый подход будет иметь некоторые накладные расходы. Но вы можете использовать такую конструкцию:
Основная причина использования потоков заключается в том, что они делают ваш код более простым и легким для чтения.
источник
anyMatch
ярлык дляfilter(...).findFirst().isPresent()
?Цель потоков в Java - упростить написание параллельного кода. Он вдохновлен функциональным программированием. Последовательный поток предназначен только для того, чтобы код был чище.
Если нам нужна производительность, мы должны использовать parallelStream, для которого был разработан. Серийный вообще медленнее.
Есть хорошая статья, о которой стоит прочитать , а производительность
ForLoop
Stream
ParallelStream
.В вашем коде мы можем использовать методы завершения, чтобы остановить поиск при первом совпадении. (anyMatch ...)
источник
Как уже упоминалось, многие хорошие моменты, но я просто хочу упомянуть ленивую оценку при оценке потока. Когда мы делаем
map()
для создания потока путей в нижнем регистре, мы не создаем весь поток сразу, вместо этого поток создается лениво , поэтому производительность должна быть эквивалентна традиционному циклу for. Он не выполняет полное сканирование,map()
аanyMatch()
выполняется одновременно. Как толькоanyMatch()
возвращается истина, он будет замкнут накоротко.источник