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;
}
예-For-Each 루프
private boolean isExcluded(String path){
for (String excludePath : EXCLUDE_PATHS) {
if(path.contains(excludePath.toLowerCase())){
return true;
}
}
return false;
}
참고 것을 path
매개 변수는 항상 소문자 .
내 첫 번째 추측은 for-each 접근 방식이 더 빠르다는 것입니다. 조건이 충족되면 루프가 즉시 반환되기 때문입니다. 필터링을 완료하기 위해 스트림은 모든 목록 항목을 계속 반복합니다.
내 가정이 맞습니까? 그렇다면 왜 (또는 오히려 언제 ) 사용 stream()
합니까?
답변
당신의 가정이 맞습니다. 스트림 구현이 for 루프보다 느립니다.
이 스트림 사용은 for 루프만큼 빠릅니다.
EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.anyMatch(path::contains);
이것은 항목을 반복 String::toLowerCase
하고 항목에 하나씩 적용 하고 필터를 적용 하고 첫 번째 항목에서 종료합니다. 일치 .
collect()
& 둘 다 anyMatch()
터미널 작업입니다. anyMatch()
그러나 collect()
모든 항목을 처리해야하는 동안 처음 발견 된 항목에서 종료 됩니다.
답변
Streams 사용 여부는 성능 고려가 아니라 가독성에 따라 결정해야합니다. 실제로 성능과 관련하여 다른 고려 사항이 있습니다.
당신으로 .filter(path::contains).collect(Collectors.toList()).size() > 0
접근, 당신은 모든 요소를 처리하고 임시로 수집List
하고 크기를 비교하기 전에 하지만 두 요소로 구성된 스트림에는 거의 문제가되지 않습니다.
.map(String::toLowerCase).anyMatch(path::contains)
요소 수가 상당히 많은 경우을 사용 하면 CPU주기와 메모리를 절약 할 수 있습니다. 그래도 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);
}
대신. 따라서를 호출 할 때마다 소문자로의 변환을 반복 할 필요가 없습니다 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
플래그를 사용 하여 문자열을 정규식 패턴으로 컴파일하면 일반 문자열 작업처럼 동작하지만 엔진이 예를 들어 Boyer Moore 알고리즘을 사용하여 준비하는 데 약간의 시간을 소비하여 실제 비교와 관련하여 더 효율적입니다.
물론 이것은 준비에 소요되는 시간을 보상 할 수있는 충분한 후속 테스트가있는 경우에만 효과가 있습니다. 이 작업이 성능에 매우 중요한지 여부를 묻는 첫 번째 질문 외에 실제 성능 고려 사항 중 하나입니다. Streams를 사용할지 아니면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 …)
답변
다른 사람들이 많은 좋은 점을 언급했듯이 스트림 평가에서 지연 평가 를 언급하고 싶습니다 . map()
소문자 경로의 스트림을 생성 할 때 전체 스트림을 즉시 생성하지 않고 대신 스트림이 느리게 구성 되므로 성능이 기존 for 루프와 동일해야합니다. 그것은 전체 검사를 수행하지 않는, map()
그리고 anyMatch()
같은 시간에 실행됩니다. anyMatch()
true를 반환 하면 단락됩니다.