자료구조

자바 스트림(Stream)이란?

Z00_HWAN_99 2024. 7. 3. 23:14
728x90
반응형

많은 알고리즘 문제와 코딩 테스트를 풀다 보면, 거의 항상 "입력은 여러 테스트 케이스로 이루어져 있다" 혹은 "입력으로 무엇 무엇이 들어온다"라는 말과 함께 데이터를 사용자로부터 받는 경우가 대다수입니다. 이때 저희는 파일이나 콘솔을 활용하여 입출력을 하지만, 실제 내부에서는 스트림(Stream)이라는 흐름을 통해 다뤄집니다. 이 흐름을 통해 입출력을 받고 생성한 데이터를 기반으로 가공하여 결과를 도출해냅니다. 그러면 이 스트림(Stream)이라는 것이 과연 무엇이냐? 지금부터 한번 알아보겠습니다🤗

 

스트림(Stream)이란? -> Stream : 개울, 시내, (액체&기체의)줄기

  • 실제의 입력이나 출력이 표현된 데이터의 흐름을 의미.
  • 운영체제에 의해 생성되는 가상의 연결 고리(중간 매개자 역할)를 의미.
  • Java SE 8 부터 Stream API 도입

 

등장 배경?

  • 자바에서는 많은 양의 데이터를 저장하기 위해 배열이나 컬렉션(List, Map)을 사용함. 하지만 이렇게 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(iterator)를 사용하여 새로운 코드를 반복적으로 작성하게됨.
  • 또한, 이 과정으로 인해 길어진 코드는 가독성과 코드 재활용성이 떨어지며, 데이터베이스의 쿼리문 처럼 정형화된 패턴을 가지지 못함.

 

컬렉션 VS 스트림(둘의 가장 큰 차이는 데이터가 언제 계산되느냐..!!)

  • 컬렉션
    • 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조.
    • 컬렉션의 모든 요소가 컬렉션에 저장되기 전에 미리 계산되어야 함. ex) DVD -> 영상 전체 데이터를 CD에 담고 있음.
    • 외부 반복 : 사용자가 직접 요소를 반복.
  • 스트림
    • 요청할 때만 요소를 계산하는 고정된 자료구조.
    • 요청하는 값에 대해서만 추출됨. ex) 스트리밍 서비스 -> 사용자의 니즈에 맞는 영상 다운
    • 내부 반복 : 반복을 알아서 처리하고 결과값을 저장해줌.

 

스트림(Stream)의 Method

  • 중간 연산(Intermediate Operations) : 스트림을 return하여 연속적으로 사용 가능 -> 결과를 만들어가는 과정.
filter 조건에 맞는 요소만을 포함하는 새로운 스트림을 반환
map 각 요소를 주어진 함수에 적용한 결과로 새로운 스트림을 반환
sorted 요소를 정렬한 새로운 스트림을 반환
distinct 중복을 제거한 새로운 스트림을 반환
limit 지정된 수만큼의 요소를 포함하는 새로운 스트림을 반환
skip 처음 n개의 요소를 건너뛰고 나머지 요소를 포함하는 새로운 스트림을 반환
  • 최종 연산(Terminal Operations) : 스트림의 요소를 연산 및 가공하여 결과를 return -> 최종적으로 결과를 만들어내는 과정.
forEach 각 요소에 대해 주어진 작업을 수행
collect 스트림의 요소를 컬렉션으로 모음
reduce 스트림의 요소를 하나의 값으로 결합
count 스트림의 요소 수를 반환
anyMatch, allMatch, noneMatch 주어진 조건을 만족하는 요소가 있는지에 대한 여부 반환
findFirst, findAny 스트림의 첫번째 요소 또는 임의의 요소 반환

 

 

스트림(Stream)의 장&단점

  • 장점
    1. 간결하고 가독성 높은 코드 : 선언적 방식으로 데이터 처리를 기술하여 코드가 간결하고 이해하기 쉬워짐.(for문 & loop문 X)
    2. 병렬 처리 용이 : 스트림의 병렬 연산(parallel stream)을 통해 멀티코어 프로세서의 성능을 쉽게 활용할 수 있음.
    3. 지연 연산 : 필요할 때만 연산을 수행하므로 성능 최적화에 유리.
public class Main {
  public static void main(String[] args) {
    List<String> food = Arrays.asList("KimBab", "Ramen", "Fried rice", "Kimchi noodle"); // -> 병렬 처리
    Stream<String> stream = food.stream()
        .filter(name -> {// -> 선언적 접근 방식, 병렬 처리
          System.out.println("Filtering " + name);
          return name.startsWith("K");
        });
    System.out.println("Stream created");
    System.out.println("Filtered count : " + stream.count());// -> 지연 연산
  }
}
//실행결과
//Stream created
//Filtering KimBab
//Filtering Ramen
//Filtering Fried rice
//Filtering Kimchi noodle
//Filtered count : 2
  • 단점
    1. 디버깅 어려움 : 선언적 코딩 스타일로 인해 디버깅이 어렵고, 스택 트레이스가 복잡해질 수 있음.
      +) 스택 트레이스란? -> 프로그램의 실행 과정에서 호출된 메서드들의 순서와 위치 정보.
    2. 성능 저하 가능성 : 병렬 스트림의 과도한 사용이나 부적절한 사용은 오히려 성능을 저하시킬 수 있음.
    3. 학습 곡선 : 기존의 루프 기반 프로그래밍에 익숙한 개발자에게는 함수형 프로그래밍 패러다임을 이해하는 데 시간이 걸릴 수 있음.

?????

  • 분명 장점에서 성능 최적화에 유리하다고 했는데, 왜 단점에서는 성능을 저하 시킬 수 있다고 함?모순 아님?
    • 작은 데이터량에서의 잦은 병렬처리는 괜한 스레드 관리 오버헤드가 더 커질 수 있음.
    • 큰 데이터량에서의의 부적절한 연산의 순서 ex) map -> filtering -> count 보다는 filtering -> map -> count
    • 스트림 내부에서 외부 상태를 변경하거나 동기화가 필요한 작업을 수행하면 성능에 악영향을 미칠 수 있음.
public class Main {
  public static void main(String[] args) {
    List<String> foods = Arrays.asList("KimBab", "Ramen", "Fried rice", "Kimchi noodle");
    List<Integer> lengths = new ArrayList<>();
    // 잘못된 사용: 외부 상태 변경
    foods.parallelStream().forEach(name -> lengths.add(name.length())); // 동기화 문제 발생 가능, 성능 저하
    System.out.println(lengths);
  }
}

 

 

데이터를 추상화하여 다루고 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터 등 다양한 방식으로 저장된 데이터를 읽고 쓰기 위한 공통된 방법을 제공하기 때문에 Stream을 잘 활용하면 복잡한 데이터 처리 작업을 보다 간단하고 효율적으로 구현할 수 있습니다. 그러나 잘못 사용하면 성능이 오히려 저하될 수 있습니다. 따라서 데이터의 특성과 연산의 복잡성을 고려하여 순차 스트림과 병렬 스트림을 적절히 선택하고, 지연 연산을 활용하여 불필요한 연산을 줄이는 것이 핵심이라 할 수 있겠습니다.

https://github.com/bottomsUp-99?tab=repositories

 

bottomsUp-99 - Overview

Backend Developer. bottomsUp-99 has 10 repositories available. Follow their code on GitHub.

github.com

 

 

728x90
반응형