들어가며
Java에서 스트림(Stream)은 두 가지 맥락에서 등장합니다.
하나는 입출력(I/O) 스트림, 다른 하나는 Java 8에서 추가된 컬렉션 처리용 Stream입니다.
이 글에서는 두 가지 스트림의 개념과 병렬처리까지 함께 정리해보겠습니다.
I/O 스트림이란?
I/O 스트림은 데이터의 흐름을 관장하는 클래스입니다.
파일 읽기/쓰기, 네트워크 통신, 콘솔 입출력 등 데이터가 이동하는 모든 곳에서 사용합니다.
물이 파이프를 통해 흐르듯, 데이터가 스트림을 통해 흘러간다고 이해하면 됩니다.
[입력 스트림] 파일/네트워크/키보드 ──▶ 프로그램
[출력 스트림] 프로그램 ──▶ 파일/네트워크/화면
I/O 스트림의 종류
Java의 I/O 스트림은 처리하는 단위에 따라 두 가지로 나뉩니다.
| 구분 | 바이트 스트림 | 문자 스트림 |
|---|---|---|
| 처리 단위 | 1byte | 2byte (문자) |
| 입력 클래스 | InputStream |
Reader |
| 출력 클래스 | OutputStream |
Writer |
| 사용 예시 | 이미지, 동영상, 바이너리 파일 | 텍스트 파일, 한글 처리 |
I/O 스트림 코드 예시
파일 읽기 - FileReader
import java.io.*;
public class FileReadExample {
public static void main(String[] args) {
// try-with-resources: 자동으로 스트림 닫음
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
파일 쓰기 - FileWriter
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("안녕하세요.");
bw.newLine();
bw.write("Java 스트림 예시입니다.");
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader / BufferedWriter는 버퍼를 사용해 데이터를 모아서 처리하기 때문에 성능이 훨씬 좋습니다.
Java 8 Stream API란?
Java 8에서 추가된 Stream API는 I/O 스트림과는 다른 개념입니다.
컬렉션(List, Set 등)의 데이터를 함수형 방식으로 처리하는 기능입니다.
반복문 없이 데이터 필터링, 변환, 집계 작업을 간결하게 작성할 수 있습니다.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 기존 방식 - 반복문
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
if (n % 2 == 0) {
result.add(n * 2);
}
}
// Stream API 방식
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 짝수만 필터
.map(n -> n * 2) // 2배로 변환
.collect(Collectors.toList()); // 리스트로 수집
Stream API 주요 메서드
List<String> names = List.of("홍길동", "김철수", "이영희", "박민수");
// filter - 조건에 맞는 요소만 선택
names.stream()
.filter(name -> name.startsWith("김"))
.forEach(System.out::println); // "김철수"
// map - 요소를 변환
names.stream()
.map(name -> name + "님")
.forEach(System.out::println); // "홍길동님", "김철수님" ...
// sorted - 정렬
names.stream()
.sorted()
.forEach(System.out::println);
// count - 개수 집계
long count = names.stream()
.filter(name -> name.length() == 3)
.count();
병렬처리(Parallel Stream)
병렬 스트림은 데이터를 여러 스레드가 나눠서 동시에 처리하는 방식입니다.stream() 대신 parallelStream()을 사용하면 됩니다.
[일반 스트림]
데이터: 1 2 3 4 5 6 7 8 9 10
스레드 1개가 순서대로 처리 ──▶ 결과
[병렬 스트림]
데이터: 1 2 3 4 5 6 7 8 9 10
스레드 1: 1 2 3 ──▶┐
스레드 2: 4 5 6 ──▶├──▶ 결과 합산
스레드 3: 7 8 9 10 ──▶┘
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 일반 스트림
long sum = numbers.stream()
.mapToLong(Integer::longValue)
.sum();
// 병렬 스트림 - parallelStream()으로만 바꾸면 됨
long sumParallel = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
병렬처리 주의사항
병렬 스트림이 항상 빠른 것은 아닙니다.
// 잘못된 병렬처리 - 공유 변수 수정 시 데이터 꼬임
List<Integer> result = new ArrayList<>();
numbers.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(result::add); // 여러 스레드가 동시에 add → 결과 불안정!
// 올바른 병렬처리 - collect() 사용
List<Integer> result = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // 스레드 안전하게 수집
| 상황 | 권장 방식 |
|---|---|
| 데이터 양이 적을 때 | 일반 stream() |
| 데이터 양이 많고 연산이 무거울 때 | parallelStream() |
| 순서가 보장되어야 할 때 | 일반 stream() |
| 공유 변수를 수정할 때 | 일반 stream() |
두 가지 스트림 정리
| 구분 | I/O 스트림 | Stream API |
|---|---|---|
| 목적 | 데이터 입출력 | 컬렉션 데이터 처리 |
| 등장 시기 | Java 초기 | Java 8 |
| 주요 클래스 | InputStream, Reader 등 |
Stream<T> |
| 병렬 처리 | 직접 스레드 관리 | parallelStream() |
| 사용 예시 | 파일 읽기/쓰기, 네트워크 | 필터, 정렬, 집계 |
정리
- I/O 스트림은 데이터의 입출력 흐름을 관장하는 클래스이며, 바이트 스트림과 문자 스트림으로 나뉜다.
- Java 8 Stream API는 컬렉션 데이터를 함수형 방식으로 간결하게 처리하는 기능이다.
parallelStream()을 사용하면 여러 스레드가 데이터를 나눠 동시에 처리하는 병렬처리가 가능하다.- 병렬 스트림은 데이터 양이 많고 연산이 무거울 때 유리하며, 공유 변수 수정이나 순서 보장이 필요한 경우에는 일반 스트림을 사용해야 한다.
'-- 오늘 있었던 개발 일기' 카테고리의 다른 글
| Object 클래스 - Java 최상위 클래스 (0) | 2026.04.06 |
|---|---|
| 상대경로와 절대경로 (0) | 2026.04.04 |
| 추상 메서드(Abstract Method)란? (0) | 2026.03.31 |
| 객체지향 프로그래밍(OOP)이란? (0) | 2026.03.19 |
| 참조 타입(Reference Type)이란? (0) | 2026.03.14 |