Notice
Recent Posts
Recent Comments
«   2025/02   »
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
Archives
Today
Total
관리 메뉴

SYDev

[이것이 자바다] Chapter 17. 스트림 요소 처리 본문

Programming Lang/Java

[이것이 자바다] Chapter 17. 스트림 요소 처리

시데브 2025. 2. 3. 19:43

1. 스트림이란?

  • Java 8부터 컬렉션 및 배열의 요소를 반복 처리하기 위해 스트림 사용 가능
  • List 컬렉션의 stream() method로 Stream 객체를 얻고, forEach() method로 요소를 어떻게 처리할지를 람다식으로 제공
  • Iterator와 차이점
    • 내부 반복자이므로, 처리 속도가 빠르고 병렬 처리에 효율적
    • 람다식으로 다양한 요소 처리를 정의 가능
    • 중간 처리최종 처리를 수행하도록 파이프 라인 형성 가능
Stream<String> stream = list.stream();
stream.forEach( item -> item 처리 );

- 예제

package ch17.sec1.exam1;

import java.util.*;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		// Set collection 생성
		Set<String> set = new HashSet<>();
		set.add("홍길동");
		set.add("신용권");
		set.add("감자바");
		
		// stream을 이용한 요소 반복 처리
		Stream<String> stream = set.stream();
		stream.forEach( name -> System.out.println(name));
	}
}

 

2. 내부 반복자

  • 외부 반복자: 요소를 collection 바깥쪽으로 반복해서 가져와 처리 - for 문, Iterator
    • collection의 요소를 외부로 가져오는 코드, 처리하는 코드를 모두 개발자 코드가 가지고 있어야 함
    • 요소를 하나씩 순차적으로 처리
  • 내부 반복자: 요소 처리 방법을 collection 내부로 주입시켜, 요소를 반복 처리 - stream
    • 개발자 코드에서 제공한 데이터 처리 코드(람다식)를 가지고 컬렉션 내부에서 요소를 반복 처리
    • 요소들을 분배시켜 병렬 작업 ->  멀티 코어 CPU를 최대한 활용

- 예제

package ch17.sec2;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class ParallelStreamExample {
	public static void main(String[] args) {
		// List Collection 생성
		List<String> list = new ArrayList<>();
		list.add("name1");
		list.add("name2");
		list.add("name3");
		list.add("name4");
		list.add("name5");
		
		// 병렬 처리
		Stream<String> parallelStream = list.parallelStream();	// 병렬 스트림 얻기
		parallelStream.forEach( name -> {
			System.out.println(name + ": " + Thread.currentThread().getName());
		} );
	}
}

 

3. 중간 처리와 최종 처리

  • stream은 하나 이상 연결될 수 있고, 이를 Stream Pipelines라 부름
  • 오리지널 스트림과 집계 처리 사이의 중간 스트림들은 최종 처리를 위해 요소를 걸러내거나(필터링), 요소를 변환시키거나(매핑), 정렬하는 작업을 수행
  • 최종 처리는 중간 처리에서 정제된 요소들을 반복하거나, 집계(카운팅, 총합, 평균) 작업을 수행

 

  • mapToInt(): 객체를 int 값으로 매핑해서, IntStream으로 변환
    • 어떤 객체를 어떤 int 값으로 매핑할 것인지는 람다식으로 제공
  • IntStream.average(): 요소들의 평균 값 계산
// Student Stream
Stream<Student> studentStream = list.stream();

// score Stream
IntStream scoreStream = studentStream.mapToInt( student -> student.getScore() );

// average
double avg = scoreStream.average().getAsDouble();

  • 메소드 체이닝 패턴을 이용하여 간결화 가능
  • 스트림 파이프라인으로 구성할 때, 파이프라인의 맨 끝에는 반드시 최종 처리 부분이 존재해야 함
doube avg = list.stream()
    .mapToInt(student -> student.getScore())
    .average()
    .getAsDouble();

- 예제

package ch17.sec3;

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 int getScore() { return score; }
}

package ch17.sec3;

import java.util.Arrays;
import java.util.List;

public class StreamPipeLineExample {
	public static void main(String[] args) {
		List<Student> list = Arrays.asList(
				new Student("name1", 10),
				new Student("name2", 20),
				new Student("name3", 30)
		);
		
		double avg = list.stream()
				.mapToInt( student -> student.getScore())
				.average()
				.getAsDouble();
		
		System.out.println(avg);
	}
}

 

4. 리소스로부터 스트림 얻기

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

  • java.util.Collection interface는 stream과 parallelStream() method를 가짐 -> 자식 인터페이스인 List와 Set 인터페이스를 구현한 모든 Collection에서 객체 스트림을 얻을 수 있음

- 예제

package ch17.sec4.exam1;

public class Product {
	private int pno;
	private String name;
	private String company;
	private int price;
	
	public Product(int pno, String name, String company, int price) {
		this.pno = pno;
		this.name = name;
		this.company = company;
		this.price = price;
	}
	
	public int getPno() { return pno; }
	public String getName() { return name; }
	public String getCompany() { return company; }
	public int getPrice() { return price; }
	
	@Override
	public String toString() {
		return new StringBuilder()
				.append("{")
				.append("pno: " + pno + ", ")
				.append("name: " + name + ", ")
				.append("company: " + company + ", ")
				.append("price: " + price + ", ")
				.append("}")
				.toString();
	}
}

package ch17.sec4.exam1;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		// List collection 생성
		List<Product> list = new ArrayList<>();
		for(int i = 1; i <= 5; i++) {
			Product product = new Product(i, "상품" + i, "멋진 회사 ", (int)(10000 * Math.random()));
			list.add(product);
		}
		
		// 객체 스트림 얻기
		Stream<Product> stream = list.stream();
		stream.forEach(p -> System.out.println(p));
	}
}

4.2. 배열로부터 스트림 얻기

  • java.util.Arrays 클래스를 이용하면 다양한 종류의 배열로부터 스트림을 얻을 수 있음

- 예제

package ch17.sec4.exam2;

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) {
		String[] strArray = { "name1", "name2", "name3" };
		Stream<String> strStream = Arrays.stream(strArray);
		strStream.forEach(item -> System.out.print(item + ","));
		System.out.println();
		
		int[] intArray = { 1, 2, 3, 4, 5 };
		IntStream intStream = Arrays.stream(intArray);
		intStream.forEach(item -> System.out.print(item + ","));
		System.out.println();
	}
}

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

  • IntStream 혹은 LongStream의 static method인 range()와 rangeClosed() method -> 특정 범위의 정수 스트림을 얻을 수 있음
  • 끝 수를 포함하면 range(), 포함하지 않으면 rangeClosed()
package ch17.sec4.exam3;

import java.util.stream.IntStream;

public class StreamExample {
	public static int sum;
	
	public static void main(String[] args) {
		IntStream stream = IntStream.rangeClosed(1, 100);
		stream.forEach(a -> sum += a);
		System.out.println("sum: " + sum);
	}
}

4.4. 파일로부터 스트림 얻기

  • java.nio.file.Files의 lines() method-> 텍스트 파일의 행 단위 스트림을 얻을 수 있음

- 예제

package ch17.sec4.exam4;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class StreamExample {
	public static void main(String[] args) throws Exception {
		Path path = Paths.get(StreamExample.class.getResource("data.txt").toURI());
		Stream<String> stream = Files.lines(path, Charset.defaultCharset());
		stream.forEach(line -> System.out.println(line));
		stream.close();
	}
}

 

5. 요소 걸러내기(필터링)

  • distinct(): 요소의 중복 제거
    • 객체 스트림(Stream)일 경우, equals() method의 리턴값이 true이면 동일한 요소로 판단
    • IntStream, LongStream, DoubleStream일 경우, 값은 값이면 중복 제거
  • filter(): 매개값으로 주어진 Predicate가 true를 리턴하는 요소만 필터링
    • Predicate: 함수형 인터페이스로, 매개값을 조사한 후 boolean을 리턴하는 test() method를 가짐

 

Predicate<T>를 람다식으로 표현하면 아래와 같음

T -> { ... return true }

T -> true;    // return 문만 있을 경우, 중괄호와 return 키워드 생략 가능

 

package ch17.sec5;

import java.util.List;
import java.util.ArrayList;

public class FilteringExample {
	public static void main(String[] args) {
		// List Collection 생성
		List<String> list = new ArrayList<>();
		list.add("name1");	list.add("name2");
		list.add("name3");	list.add("name2"); list.add("name5");
		list.add("name3");	list.add("name23");
		
		// 중복 요소 제거
		list.stream()
			.distinct()
			.forEach(n -> System.out.println(n));
		System.out.println();
		
		// 3으로 끝나는 요소만 필터링
		list.stream()
			.filter(n -> n.endsWith("3"))
			.forEach(n -> System.out.println(n));
		System.out.println();
		
		// 중복 요소를 먼저 제거하고, 3으로 끝나는 요소만 필터링
		list.stream()
			.distinct()
			.filter(n -> n.endsWith("3"))
			.forEach(n -> System.out.println(n));
	}
}

 

6. 요소 변환(매핑)

  • mapping: 스트림의 요소를 다른 요소로 변환하는 중간 처리 기능
  • mapXxx(), asDoubleStream(), asLongStream(), boxed(), flatMapXxx(), ...

6.1. 요소를 다른 요소로 변환

  • mapXxx(): 요소를 다른 요소로 변환한 새로운 스트림을 리턴

  • 매개타입인 Function은 함수형 인터페이스로, 다음과 같은 종류가 존재
  • applyXxx(): 매개값을 리턴값으로 매핑(변환)

T -> { ... return R; }

T -> R;    // return 문만 있을 경우, 중괄호와 return 키워드 생략 가능

- 예제

package ch17.sec6.exam1;

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 int getScore() { return score; }
}

package ch17.sec6.exam1;

import java.util.List;
import java.util.ArrayList;

public class MapExample {
	public static void main(String[] args) {
		// List collection 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("name1", 85));
		studentList.add(new Student("name1", 92));
		studentList.add(new Student("name1", 87));
		
		// Student를 score 스트림으로 변환
		studentList.stream()
			.mapToInt(s -> s.getScore())
			.forEach(score -> System.out.println(score));
	}
}

 

 

기본 타입 간의 변환이거나, 기본 타입 요소를 Wrapper 객체 요소로 변화하려면 다음 간편화 메소드 사용

6.2. 요소를 복수 개의 요소로 변환

  • flatMapXxx(): 하나의 요소를 복수 개의 요소들로 변환한 새로운 스트림 리턴

- 예제

package ch17.sec6.exam3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FlatMappingExample {
	public static void main(String[] args) {
		// 문장 스트림을 단어 스트림으로 변환
		List<String> list1 = new ArrayList<>();
		list1.add("this is java");
		list1.add("i am a best developer");
		list1.stream().
			flatMap(data -> Arrays.stream(data.split(" ")))
			.forEach(word -> System.out.println(word));
		
		System.out.println();
		
		// 문자열 숫자 목록 스트림을 숫자 스트림으로 변환
		List<String> list2 = Arrays.asList("10, 20, 30", "40, 50");
		list2.stream()
			.flatMapToInt(data -> {
				String[] strArr = data.split(",");
				int[] intArr = new int[strArr.length];
				for(int i = 0; i < strArr.length; i++) {
					intArr[i] = Integer.parseInt(strArr[i].trim());
				}
				return Arrays.stream(intArr);
			})
			.forEach(number -> System.out.println(number));
	}
}

 

7. 요소 정렬

7.1. Comparable 구현 객체의 정렬

  • 스트림의 요소가 객체일 경우, 객체가 Comparable을 구현한 상태여야 sorted() method를 사용하여 정렬이 가능
    • 그렇지 않다면 ClassCastException 발생

  • 내림차순으로 정렬하고 싶은 경우, Comparator.reverseOrder() method가 리턴하는 Comparator를 매개값으로 제공
Stream<Xxx> reverseOrderedStream = stream.sorted(Comparator.reverseOrder());

- 예제

package ch17.sec7.exam1;

import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;

public class SortingExample {
	public static void main(String[] args) {
		// List collection 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("name1", 30));
		studentList.add(new Student("name2", 10));
		studentList.add(new Student("name3", 20));
		
		// 점수를 기준으로 오름차순으로 정렬한 새 스트림 얻기
		studentList.stream()
			.sorted()
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
		
		System.out.println();
		
		// 점수를 기준으로 내림차순으로 정렬한 새 스트림 얻기
		studentList.stream()
			.sorted(Comparator.reverseOrder())
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
	}
}

7.2. Comparator를 이용한 정렬

  • 요소 객체가 Comparable을 구현하고 있지 않다면, 비교자를 제공하면 요소 정렬 가능
  • 비교자: Comparator interface를 구현한 객체
sorted((o1, o2) -> { ... })

- 예제

package ch17.sec7.exam2;

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 int getScore() { return score; }
}

package ch17.sec7.exam2;

import java.util.List;
import java.util.ArrayList;

public class SortingExample {
	public static void main(String[] args) {
		// List collection 생성
		List<Student> studentList = new ArrayList<>();
		studentList.add(new Student("name1", 30));
		studentList.add(new Student("name2", 10));
		studentList.add(new Student("name3", 20));
		
		// 점수 기준 오름차순 새 스트림 얻기
		studentList.stream()
			.sorted((s1, s2) -> Integer.compare(s1.getScore(), s2.getScore()))
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
		
		System.out.println();
		
		// 점수 기준 오름차순 새 스트림 얻기
		studentList.stream()
			.sorted((s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()))
			.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
		
	}
}

 

8. 요소를 하나씩 처리(루핑)

  • looping: 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것
  • looping methods peek(), forEach()
    • peek(): 중간 처리 메소드, 최종 처리가 뒤에 붙지 않으면 동작 X
    • forEach(): 최종 처리 메소드

  • 매개타입 Consumer는 함수형 인터페이스로, 아래와 같은 종류 존재
    • accept(): 매개값을 처리(소비)

T -> { ... }

T -> 실행문;    // 하나의 실행문만 있을 경우 중괄호 생략

- 예제

package ch17.sec8;

import java.util.Arrays;

public class LoopingExample {
	public static void main(String[] args) {
		int[] intArr = { 1, 2, 3, 4, 5 };
		
		// 잘못 작성한 경우
		Arrays.stream(intArr)
			.filter(a -> a % 2 == 0)
			.peek(n -> System.out.println(n));	// 최종 처리가 없으므로 동작 X
		
		// 중간 처리 메소드 peek()을 이용하여 반복 처리
		int total = Arrays.stream(intArr)
				.filter(a -> a % 2 == 0)
				.peek(n -> System.out.println(n))
				.sum();	// 최종 처리
		System.out.println("sum: " + total);
		
		// 최종 처리 메소드 forEach()를 이용해서 반복 처리
		Arrays.stream(intArr)
			.filter(a -> a % 2 == 0)
			.forEach(n -> System.out.println((n)));	// 최종 처리이므로 동작함
	}
}

 

9. 요소 조건 만족 여부(매칭)

  • Matching: 요소들이 특정 조건에 만족하는지 여부를 조사하는 최종 처리 기능

 

10. 요소 기본 집계

  • Aggregate: 최종 처리 기능으로, 요소들을 처리해서 카운팅, 합계, 평균값, 최대값, 최소값 등과 같이 하나의 값으로 산출하는 것
  • 대량의 데이터를 가공해서 하나의 값으로 축소하는 reduction

10.1. 스트림이 제공하는 기본 집계

long count = Arrays.stream(arr)
    .filter(n -> n % 2 == 0)
    .average()
    .getAsDouble();

10.2. Optional 클래스

  • Optional, OptionalDouble, OptionalInt, OptionalLong 클래스는 단순히 집계값만 저장하는 것이 아니라, 집계값이 존재하지 않을 경우 디폴트 값을 설정하거나 집계값을 처리하는 Consumer를 등록 가능
  • 컬렉션에 요소가 존재하지 않으면 집계 값을 산출할 수 없으므로 NoSuchElementException 예외 발생
    • 예외 발생을 막는 3가지 방법

1) isPresent() method가 true를 리턴할 때만 집계값을 얻는다.

OptionalDouble optional = stream
    .average();
if(optional.isPresent()) {
    System.out.println("평균: " + optional.getAsDouble());
} else {
    System.out.println("평균: 0.0");
}

2) orElse() method로 집계값이 없을 경우를 대비해서 디폴트 값을 정해놓는다.

double avg = stream
    .average()
    .orElse(0.0);
System.out.println("평균: " + avg);

3) ifPresent() method로 집계값이 있을 경우에만 동작하는 Consumer 람다식을 제공

stream
    .average()
    .ifPresent(a -> System.out.println("평균: " + a));

 

11. 요소 커스텀 집계

  • 스트림은 기본 집계 메소드인 sum(), average(), count(), max(), min()을 제공하지만, 다양한 집계 결과물을 만들 수 있도록 reduce() method도 제공

  • 매개값인 BinaryOperator()는 함수형 인터페이스
  • apply(): 두 개의 매개값을 받아 하나의 값을 리턴
(a, b) -> { ... return 값 }

(a, b) -> 값    // return 문만 있을 경우 중괄호와 return 키워드 생략 가능

 

  • reduce()는 스트림에 요소가 없을 경우 예외 발생, but identity 매개값이 주어지면 이 값을 디폴트로 리턴

오른쪽 경우에는 디폴트값인 0 리턴

int sum1 = studentList.stream()
                .mapToInt(Student :: getScore)
                .reduce(0, (a, b) -> a + b);    // .sum()과 동일

 

12. 요소 수집

  • collect(): 요소들을 필터링 또는 매핑한 후 요소들을 수집하는 최종 처리 메소드
    • 필요한 요소만 collection에 담을 수 있고, 요소들을 grouping한 후에 집계도 가능

12.1. 필터링한 요소 수집

  • Stream의 collect(Collector<T, A, R> collector) method: 필터링 또는 매핑된 요소들을 새로운 컬렉션에 수집하고, 이 컬렉션을 리턴
  • Collector: 어떤 요소어떤 컬렉션에 수집할 것인지 결정
    • T: 요소
    • A: 누적기(accumulator)
    • R: 요소가 저장될 collection
    • T 요소를 A 누적기가 R에 저장

  • Collector의 구현 객체는 다음과 같이, Collectors class의 static method로 얻을 수 있음
    • A(누적기)가 ?로 설정된 경우 -> Collector가 List, Set, Map collection에 요소를 저장하는 방법을 알고 있어 별도의 누적기 필요 X

 

List<Student> maleList = totalList.stream()
    .filter(s -> s.getSex().equals("남"))
    .collect(Collectors.toList());
    
Map<String, Integer> map = totalList.stream()
    .collect(
        Collectors.toMap(
            s -> s.getName(),    // Student 객체에서 키가 될 부분 리턴
            s -> s.getScore()    // Student 객체에서 값이 될 부분 리턴
        )
    );

 

Java 16부터는 좀 더 편리하게 요소 스트림에서 List collection을 얻을 수 있음 -> Stream의 toList() method

List<Student> maleList = totalList.stream()
    .filter(s -> s.getSex().equals("남"))
    .toList();

12.2. 요소 그룹핑

  • collect() method 요소들을 그룹핑해서 Map 객체를 생성하는 기능 제공 -> Collectors.groupingBy() method에서 얻은 Collector를 collect() method를 호출할 때 제공
    • T를 K로 매핑
    • K를 키로 해 List<T> 값으로 갖는 Map Collection 생성

Map<String, List<Student>> map = totalList.stream()
    .collect(
        Collectors.groupingBy(s -> s.getSex())    // grouping key return
    );
    
List<Student> maleList = map.get("남");
maleList.stream().forEach(s -> System.out.println(s.getName()));
System.out.println();

List<Student> femaleList = map.get("여");
femaleList.stream().forEach(s -> System.out.println(s.getName()));

  • Collectors.groupingBy() method는 grouping 이후 매핑 및 집계(평균, 카운팅, 연결, 최대, 최소, 합계)를 수행할 수 있도록 두 번째 매개값인 Collector를 가질 수 있음
  • 다음은 두 번재 매개값으로 사용될 Collectors를 얻을 수 있는 Collectors의 static methods

Map<String, Double> map = totalList.stream()
    .collect(
        Collectors.groupingBy(
            s -> s.getSex(),
            Collectors.averagingDouble(s -> getScore())
        )
    );
    
System.out.println(map);

 

13. 요소 병렬 처리

  • Parallel Operation: 멀티 코어 CPU 환경에서 전체 요소를 분할해서 각각의 코어가 병렬적으로 처리하는 것
  • java는 parallel operation을 위해 parallel stream을 제공

13.1. 동시성과 병렬성

  • 동시성(Concurrency): multi-threads가 하나의 core에서 번갈아 가며 실행
  • 병렬성(Parallelism): multi-core를 각각 이용해서 병렬로 실행
    • 데이터 병렬성: 전체 데이터를 분할해서 서브 데이터셋으로 만들고, 이 서브 데이터셋들을 병렬 처리해서 작업을 빨리 끝내는 것
    • 자바 병렬 스트림은 데이터 병렬성을 구현한 것
    • 작업 병렬성: 서로 다른 작업을 병렬 처리

 

13.2. 포크조인 프레임워크

  • 자바 병렬 스트림은 요소들을 병렬처리하기 위해 ForkJoin Framework를 사용
  • ForkJoin Framework
    • 포크 단계에서 전체 요소들을 서브 요소셋으로 분할
    • 각각의 서브 요소셋을 멀티 코어에서 병렬로 처리
    • 조인 단계에서 서브 결과를 결합해서, 최종 결과를 만들어냄

  • ForkJoin Framework는 병렬 처리를 위해 ThreadPool을 사용
  • 각각의 코어에서 서브 요소셋을 처리하는 것은 worker thread이므로, thread 관리가 필요
  • ForkJoin Framework는 Executor의 구현 객체인 ForkJoinPool을 사용하여 worker thread 관리

13.3. 병렬 스트림 사용

  • parallelStream(): collection(List, Set)을부터 병렬 스트림을 바로 리턴
  • parallel(): 기존 스트림을 병렬 처리 스트림으로 변환

- 예제

package ch17.sec13;

import java.util.*;
import java.util.stream.Stream;

public class ParallelExample {
	public static void main(String[] args) {
		Random random = new Random();
		
		// 1억 개의 Integer 객체 저
		List<Integer> scores = new ArrayList<>();
		for(int i = 0; i < 100000000; i++) {
			scores.add(random.nextInt(101));
		}
		
		double avg = 0.0;
		long startTime = 0;
		long endTime = 0;
		long time = 0;
		
		// 일반 스트림으로 처
		Stream<Integer> stream = scores.stream();
		startTime = System.nanoTime();
		avg = stream
				.mapToInt(i -> i.intValue())
				.average()
				.getAsDouble();
		endTime = System.nanoTime();
		time = endTime - startTime;
		System.out.println("avg: " + avg + ", 일반 스트림 처리 시간: " + time + "ns");
		
		Stream<Integer> parallelStream = scores.parallelStream();
		startTime = System.nanoTime();
		avg = parallelStream
				.mapToInt(i -> i.intValue())
				.average()
				.getAsDouble();
		endTime = System.nanoTime();
		time = endTime - startTime;
		System.out.println("avg: " + avg + ", 일반 스트림 처리 시간: " + time + "ns");
	}
}

13.4. 병렬 처리 성능

병렬 처리에 영향을 미치는 3가지 요인

1) 요소의 수와 요소당 처리 시간

  • 컬렉션에 전체 요소의 수가 적고요소당 처리 시간이 짧으면 일반 스트림이 병렬 스트림보다 빠를 수 있음
    • 병렬 처리는 포크 및 조인 단계 존재하고, threadPool을 생성하는 추가적 비용 발생하기 때문

2) 스트림 소스의 종류

  • ArrayList배열: index로 요소를 관리하기 때문에, 포크 단계에서 요소를 쉽게 분리할 수 있어 병렬 처리 시간이 절약
  • HashSet, TreeSet, LinkedList: 요소 분리가 쉽지 않아, 상대적으로 병렬 처리가 늦음

3) 코어의 수

  • CPU core의 수가 많을수록 병렬 스트림의 성능은 좋아짐
  • CPU core의 수가 적을 경우, 일반 스트림이 더 빠를 수 있음
    • 병렬 스트림은 threads 수가 증가 -> 동시성(concurrency)이 많이 일어나므로, 오히려 느려짐

참고자료