ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 스트림과 병렬 처리 - 스트림
    CSE/Java 2015. 9. 8. 18:04
    스트림과 병렬 처리는 여러 절로 구성되어 있습니다.








    1. 스트림 소개

     스트림(Stream)은 자바 8부터 추가된 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식(functional-style)으로 처리할 수 있도록 해주는 반복자입니다.


     



     1.1 반복자 스트림

      자바 7 이전까지는 List<String> 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator 반복자를 다음과 같이 사용해왔습니다.



    1
    2
    3
    4
    5
    6
    7
    8
    List<String> list = Arrays.asList("John""Simons""Andy");
    Iterator<String> iterator = list.iterator();
     
    while (iterator.hasNext()) {
        String name = iterator.next();
        System.out.println(name);
    }
     
    cs




      이 코드를 Stream을 사용해서 변경하면 다음과 같습니다.


    1
    2
    3
    4
    5
    List<String> list = Arrays.asList("John""Simons""Andy");
    Stream<String> stream = list.stream();
     
    stream.forEach( name -> System.out.println(name) );
     
    cs




      컬렉션(java.util.Collection)의 stream() 메소드로 스트림 객체를 얻고 나서 stream.forEach (name -> System.out.println(name) ); 메소드를 통해 컬렉션의 요소를 하나씩 콘솔에 출력합니다.


      forEach() 메소드는 다음과 같이 Consumer 함수적 인터페이스 타입을 파라미터로 가지므로 컬렉션의 요소를 소비할 코드를 람다식으로 기술 할 수 있습니다.




    1
    2
    void forEach(Consumer<T> action)
     
    cs




      Iterator를 사용한 코드와 Stream을 사용한 코드를 비교해 보면 Stream을 사용하는 것이 훨씬 단순해 보입니다.


      다음 예제는 List<String> 컬렉션의 String 요소를 Iterator와 Stream을 이용해서 순차적으로 콘솔에 출력합니다.



      * IteratorVsStreamExam.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package streamexam;
     
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.List;
    import java.util.stream.Stream;
     
    public class IteratorVsStreamExam {
     
        public static void main(String[] args) {
            List<String> list = Arrays.asList("John""Simons""Andy");
     
            // Iterator 이용
            Iterator<String> iterator = list.iterator();
     
            while (iterator.hasNext()) {
                String name = iterator.next();
                System.out.println(name);
            }
     
            // Stream 이용
            Stream<String> stream = list.stream();
            stream.forEach(name -> System.out.println(name));
        }
     
    }
     
    cs













     1.2 스트림의 특징

      Stream은 Iterator와 비슷한 역할을 하는 반복자이지만, 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는 점 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 많은 차이를 두고 있습니다. 이 특징에 대해 자세히 살펴보도록 하겠습니다.



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


     Stream이 제공하는 대부분의 요소 처리 메소드는 함수적 인터페이스 매개 타입을 가지기 때문에 람다식 또는 메소드 참조를 이용해서 요소 처리 내용을 파라미터로 전달할 수 있습니다.


     다음 예제는 컬렉션에 저장된 Student를 하나씩 가져와 학생 이름과 성적을 콘솔에 출력하도록 forEach() 메소드의 파라미터로 람다식을 주었습니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    package streamexam;
     
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
     
    public class LambdaExpressionsExam {
     
        public static void main(String[] args) {
            List<Student> list = Arrays.asList(new Student("John Smith"91), new Student("Andy Knight"93));
     
            Stream<Student> stream = list.stream();
            stream.forEach(s -> {
                String name = s.getName();
                int score = s.getScore();
     
                System.out.println(name + "-" + score);
            });
        }
     
    }
     
    cs




      * Student.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     
    package streamexam;
     
    public class Student {
        private String name;
        private int score;
     
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name;
        }
     
        public int getScore() {
            return score;
        }
     
        public void setScore(int score) {
            this.score = score;
        }
     
    }
     
    cs










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


     외부 반복자(external iterator)란 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴을 말합니다.


     index를 이용하는 for문 그리고 Iterator를 이용하는 while 문은 모두 외부 반복자를 이용하는 것입니다.


     반면 내부 반복자(internal Iterator)는 컬렉션 내부에서 요소들을 반복시키고, 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴을 말합니다. 


     다음 그림을 보면서 외부 반복자와 내부 반복자의 차이를 이해해보도록 합시다.








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


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


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










     스트림을 이용하면 코드로 간결해지지만, 무엇보다도 요소의 병렬 처리가 컬렉션 내부에서 처리되므로 일석이조의 효과를 가져옵니다.


     병렬(parallel) 처리란 한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것을 말합니다.


     병렬 처리 스트림을 이용하면 런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고, 서브 작업의 결과를 자동으로 결합해서 최종 결과물을 생성합니다.


     다음 예제는 순차 처리 스트림과 병렬 처리 스트림을 이용할 경우 사용된 스레드의 이름이 무엇인지 출력하는 예제입니다.



     * ParallelExam.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package stream;
     
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
     
    public class ParellelExam {
     
        public static void main(String[] args) {
            List<String> list = Arrays.asList("John""Simon""Andy""Andrew""Bill");
     
            // 순차 처리
            Stream<String> stream = list.stream();
            stream.forEach(ParellelExam::print);
     
            System.out.println();
     
            // 병렬 처리
            Stream<String> parallelStream = list.parallelStream();
            parallelStream.forEach(ParellelExam::print);
        }
     
        public static void print(String str) {
            System.out.println(str + " : " + Thread.currentThread().getName());
        }
     
    }
     
     
    cs
















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


     스트림은 컬렉션의 요소에 대해 중간 처리와 최종 처리를 수행할 수 있는데, 중간 처리에서는 매핑, 필터링, 정렬을 수행하고 최종 처리에서는 반복, 카운팅, 평균, 총합 등의 집계 처리를 수행합니다.


     예를 들어 학생 객체를 요소로 가지는 컬렉션이 있다고 가정해봅시다. 중간 처리에서는 학생의 점수를 뽑아내고, 최종 처리에서는 점수의 평균값을 산출합니다.


     다음 예제는 List에 저장되어 있는 Student객체를 중간처리해서 score 필드값에 매핑하고, 최종 처리에서 score의 평균 값을 산출하는 예제입니다.



     * MapAndReduceExam.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    package stream;
     
    import java.util.Arrays;
    import java.util.List;
     
    public class MapAndReduceExam {
     
        public static void main(String[] args) {
            List<Student> list = Arrays.asList(
                    new Student("John"76), 
                    new Student("Jack"88), 
                    new Student("Smith"100)
            );
     
            double avg = list.stream().mapToInt(Student::getScore).average().getAsDouble();
            
            System.out.println("평균 점수: " + avg);
        }
     
    }
     
    cs





















    * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.

    댓글

Designed by Tistory.