상세 컨텐츠

본문 제목

스트림과 병렬 처리

기록 - 프로그래밍/Java

by wjjun 2024. 2. 21. 13:00

본문

 

스트림

스트림은 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있게 해주는 반복자로 생각하면 됩니다.

 

반복자 스트림

List<String> 컬렉션에서 요소를 순차처리 하기 위해 Iterator 반복자를 이용하면 아래와 같습니다

// Iterator 반복자 사용
List<String> list = Arrays.asList("A", "B", "C");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    String name = iterator.net();
    sout(name);
}

// Stream 사용
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
stream.forEach(name -> sout(name));

1. 컬렉션(java.util.Collection)의 stream() 메서드로 스트림 객체를 얻고 

2. stream.forEach(name -> sout(name)) 메서드로 컬렉션 요소 하나씩 출력

 

Consumer 함수적 인터페이스 타입의 매개값을 가지므로 컬렉션 요소를 소비할 코드를 람다식으로 기술이 가능하다.

void forEach(Consumer<T> action)

 

 

스트림 특징

람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용해 병렬 처리가 쉽다. 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 차이가 크다.

 

람다식으로 요소 처리 코드를 제공한다.

Stream이 제공하는 대부분 요소 처리 메서드는 함수적 인터페이스 매개개 타입을 갖기 때문에 람다식 또는 메서드 참조를 이용해 요소 처리 내용을 매개값으로 전달이 가능하다.

Stream<Students> stream = list.stream(); // 스트림 얻기
stream.forEach(s -> { // List 컬렉션에서 Student를 가져와 람다식 매개값으로 제공
    String name = s.getName();
    int score = s.getScore();
    sout(name + " - " + score);
});

 

내부 반복자를 사용하므로 병렬 처리가 쉽다.

외부 반복자는 개발자가 코드로 직접 컬렉션 요소를 반복해서 가져오는 코드 패턴이다. index를 이용하는 for문, Iterator를 이용하는 while문 모두 외부 반복자를 이용한 것이다.

 

외부 반복자 : 개발자코드 -> next() -> 컬렉션 -> 처리 -> next() -> 컬렉션 -> 처리 -> next() -> 컬렉션 -> 처리

내부 반복자 : 개발자코드 -> 처리코드 제공 -> 컬렉션 -> 처리 -> 처리 -> 처리 -> 처리

내부 반복자를 사용해서 얻는 이점은 컬렉션 내부에서 어떻게 요소를 반복시킬 것인가는 컬렉션에 맡기고 개발자는 요소 처리 코드에만 집중할 수 있다.

내부 반복자는 요소들의 반복 순서를 변경하거나 멀티코어 CPU를 최대한 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기 때문에 하나씩 처리하는 순차적 외부 반복자보다 효율적으로 요소를 반복시킬 수 있다.

 

Iterator는 컬렉션 요소를 가져오는 것부터 처리까지 모두 개발자가 작성해야 한다. 스트림은 람다식으로 요소 처리 내용만 전달할 뿐 반복은 컬렉션 내부에서 일어난다.

스트림을 이용하면 코드도 간결하지만, 병렬 처리가 컬렉션 내부에서 처리되는 효과까지 가질 수 있다.

 

병렬처리는 한 가지 작업을 서브 작업으로 나누고

서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것을 의미합니다.

 

런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고, 서브 작업 결과를 자동으로 결합해 최종 결과물을 생성합니다. 병렬 처리 스트림을 이용하면 여러 개 스레드가 요소들을 부분적으로 합하고 이부분 합을 최종 결합해 전체 합을 생성합니다.

 

 

스트림은 중간 처리와 최종 처리를 할 수 있다

컬렉션, 배열 -> 오리지널 스트림 -> (중간처리 시작) -> 필터링 처리 중간 스트림 -> 매핑 처리 중간 스트림 -> (최종처리 시작) -> 집계 처리 결과물 -> 결과

 

Collection : 학생 객체 1, 2, 3이 있다. (객체 요소들)

중간처리 : 학생 개별 점수를 추출한다. (학생 객체를 점수로 매핑한다)

최종처리 : 점수 평균값을 산출한다. (집계)

 

dobule avg = studentList.stream()
    .mapToInt(Student :: getScore)
    .average()
    .getAsDouble();
    
    sout("평균 점수 : " + avg);

 

 

스트림 종류

java8 추가된 java.util.stream 패키지 내용을 보면 BaseStream 인터페이스를 부모로 Stream, IntStream, LongStream, DoubleStream 자식 인터페이스들이 상속 관계를 형성하고 있다.

 

Stream은 객체 요소를 처리하는 스트림으로 IntStream, LongStream, DoubleStream은 각각 int, long, double 요소를 처리하는 스트림입니다.

 

리턴 타입 메서드(매개변수) 소스
Stream<T> java.util.Collection.stream()
java.util.Collection.parallelStream()
컬렉션
Stream<T>
IntStream
LongStream
DoubleStream
Arrays.stream(T[]), Stream.of(T[]),
Arrays.stream(int[]), IntStream(int[]),
Arrays.stream(long[]), LongStream(long[]),
Arrays.stream(double[]), DoubleStream(double[])
배열
IntStream IntStream.range(int, int)
IntStream.rangeClosed(int, int)
int 범위
Stream<String> File.lines(Path, Charset)
BufferedReader.lines()
파일

 

컬렉션으로 부터 스트림 얻기

studentList.stream();

 

배열로부터 스트림 얻기

Arrays.stream(strArray);

 

숫자 범위로부터 스트림 얻기

IntStream.rangeClosed(1, 100);

 

파일로부터 스트림 얻기

Files.lines(path, Charset.defaultCharset());

 

관련글 더보기

댓글 영역