1. 정의
자바에서 스트림(Stream)은 컬렉션, 배열 또는 I/O 자원과 같은 데이터 소스를 처리하는데 사용되는 연속적인 요소 시퀀스이다. 스트림은 데이터를 처리하는데 편리한 기능을 제공하며, 람다식과 메서드 참조를 활용하여 간결하고 가독성 있는 코드를 작성할 수 있다.
2. 특징
1) 스트림은 데이터를 저장하지 않는다.
스트림은 단지 요소의 연속적인 흐름을 나타내며, 실제 데이터는 스트림을 생성한 컬렉션이나 배열에 저장되어 있다. 이는 스트림이 더 효율적으로 대용량 데이터를 처리할 수 있게 해준다.
2) 스트림은 일회용이다.
스트림은 일회용이므로 한 번 사용한 스트림은 재사용할 수 없다. 필요한 경우에는 새로운 스트림을 생성해야 한다.
3) 스트림은 연산을 지원한다.
스트림은 중간 연산(intermediate operation)과 최종 연산(terminal operation)으로 구성된다. 중간 연산은 다른 스트림을 반환하며, 최종 연산은 결과를 반환하거나 작업을 수행한다.
3. 스트림 생성 방법
스트림을 생성하는 방법에는 다양한 방법이 있다. 가장 일반적인 방법은 컬렉션(Collection)이나 배열(Array)을 스트림으로 변환하는 것이다. Java 8부터는 컬렉션 인터페이스(Collection 인터페이스를 구현한 클래스)에 stream() 메서드가 추가되어 컬렉션을 스트림으로 쉽게 변환할 수 있게 되었다.
예시 코드>
// 리스트(List)를 스트림으로 변환하는 예
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
Stream<String> stream = list.stream();
// 배열을 스트림으로 변환하는 예
String[] array = {"Apple", "Banana", "Orange"};
Stream<String> stream = Arrays.stream(array);
4. 스트림 중간 연산 및 최종 연산
스트림을 생성한 후에는 중간 연산과 최종 연산을 사용하여 스트림의 요소를 처리하고 결과를 얻을 수 있다. 중간 연산은 필터링(filtering), 매핑(mapping), 정렬(sorting) 등의 작업을 수행하며, 최종 연산은 결과를 반환하거나 작업을 수행한다.
예시 코드>
// 리스트에서 길이가 5 이상인 문자열만 필터링하여 출력하는 예
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
list.stream()
.filter(s -> s.length() >= 5)
.forEach(System.out::println); // 출력: Apple \\n Banana \\n Orange
// 스트림 매핑 예시
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(wordLengths); // 출력: [5, 6, 6]
// 스트림 정렬 예시
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 출력: [1, 2, 3, 5, 8]
// 스트림 최종 연산 예시
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sum: " + sum); // 출력: Sum: 19
5. collect 메소드
Java 스트림(Stream)의 collect 메소드는 스트림의 요소를 수집하여 원하는 결과 컨테이너(컬렉션 등)에 저장하는 역할을 수행하는 중간(intermediate) 또는 최종(terminal) 연산이다. collect 메소드는 매개변수로 Collector 인터페이스를 받으며, Collector 인터페이스를 구현한 클래스 객체를 생성하여 사용한다.
Collector 인터페이스는 다양한 연산을 수행할 수 있는 메소드들을 제공한다. 일반적으로 스트림의 요소를 그룹화, 분할, 변환, 집계 등의 작업을 수행할 때 collect 메소드와 Collector를 함께 사용한다.
Collectors 클래스는 Collector 인터페이스를 구현한 다양한 팩토리 메소드들을 제공하여 흔히 사용되는 수집 작업을 간편하게 생성할 수 있도록 도와준다.
예시 코드>
**List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Emma");
// 스트림의 요소를 대문자로 변환하여 List로 수집
List<String> uppercaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 실행 결과: List<String> uppercaseNames = [ALICE, BOB, CHARLIE, DAVID, EMMA]
// 길이별로 이름을 그룹화하여 Map으로 수집
Map<Integer, List<String>> namesByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
/* 실행 결과:
Map<Integer, List<String>> namesByLength = {
3=[Bob],
5=[Alice, David, Emma],
7=[Charlie]
}
*/
// 이름 길이의 평균을 구하고 Double로 수집
double averageNameLength = names.stream()
.collect(Collectors.averagingInt(String::length));
// 실행 결과: double averageNameLength = 5.2**
위의 예시에서 toList(), groupingBy(), averagingInt() 등은 Collectors 클래스의 팩토리 메소드를 사용하여 수집 작업을 수행하고 있다. 이렇게 사용하면 스트림의 데이터를 손쉽게 원하는 형태로 수집할 수 있다.
6. I/O 스트림
I/O 스트림은 자바에서 입력(Input)과 출력(Output)을 처리하는데 사용되는 데이터 흐름이다. I/O 스트림은 데이터를 소스에서 읽거나 대상에 쓰는 방법을 추상화하며, 다양한 유형의 입출력을 다룰 수 있도록 한다. 자바에서는 I/O 스트림을 바이트 스트림(Byte Stream)과 문자 스트림(Character Stream)으로 나눌 수 있다.
그리고, I/O 스트림은 데이터를 처리하는 방향에 따라 입력 스트림(Input Stream)과 출력 스트림(Output Stream)으로 나눌 수도 있다. 입력 스트림은 데이터 소스로부터 데이터를 읽는데 사용되며, 출력 스트림은 데이터를 대상에 쓰는데 사용된다.
또한, I/O 스트림은 버퍼링(Buffering)과 같은 추가 기능을 제공하여 데이터의 효율적인 처리를 돕는다. 버퍼링은 메모리에 일시적으로 데이터를 저장하고 일정량을 한 번에 입출력하는 방식으로 I/O 작업의 성능을 향상시킨다. BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter 등의 클래스를 사용하여 버퍼링을 적용할 수 있다.
I/O 스트림은 다양한 유형의 데이터 소스와 대상을 처리할 수 있으며, 파일, 네트워크, 키보드 입력, 메모리 등 다양한 입출력 작업에 유용하게 사용된다.
1) 바이트 스트림(Byte Stream)
바이트 스트림은 데이터를 바이트 단위로 처리하는 스트림이다. InputStream과 OutputStream 클래스를 기반으로 하며, DataInputStream, DataOutputStream, FileInputStream, FileOutputStream, ByteArrayInputStream, ByteArrayOutputStream, BufferedInputStream, BufferedOutputStream 등의 구체적인 클래스를 사용하여 파일이나 네트워크와 같은 바이트 기반의 데이터 소스와 대상을 처리할 수 있다. 바이트 스트림은 주로, 이진 데이터를 다루거나 바이너리 파일을 처리할 때 사용한다.
예시 코드>
// 바이트 스트림을 이용한 파일 복사
try (InputStream inputStream = new FileInputStream("source.txt");
OutputStream outputStream = new FileOutputStream("destination.txt")) {
int byteData;
while ((byteData = inputStream.read()) != -1) {
outputStream.write(byteData);
}
} catch (IOException e) {
e.printStackTrace();
}
// 바이트 스트림을 이용한 이미지 파일 읽기 및 쓰기
try (InputStream inputStream = new FileInputStream("image.jpg");
OutputStream outputStream = new FileOutputStream("copy_image.jpg")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
// 버퍼링 기능을 제공하는 필터 스트림
class BufferedStreamFileCopier {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("대상 파일: ");
String src = sc.nextLine();
System.out.print("사본 이름: ");
String dst = sc.nextLine();
try(BufferedInputStream in = new BufferedInputStream(new FileInputStream(src)) ;
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dst))) {
int data;
while(true) {
data = in.read();
if(data == -1)
break;
out.write(data);
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}
// 파일에 기본 자료형 데이터 저장과 동시에 버퍼링 기능 추가
class BufferedDataOutputStream {
public static void main(String[] args) {
try(DataOutputStream out =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("data.dat")))) {
out.writeInt(370);
out.writeDouble(3.14);
}
catch(IOException e) {
e.printStackTrace();
}
}
}
2) 바이트 스트림 관련 클래스
(1) InputStream / OutputStream
- InputStream 클래스는 바이트 스트림을 읽는 기능을 제공한다.
- OutputStream 클래스는 바이트 스트림을 쓰는 기능을 제공한다.
- 바이트 데이터를 다루며, 이진 파일 등을 처리할 때 사용된다.
(2) FileInputStream / FileOutputStream
- FileInputStream 클래스는 파일로부터 바이트 데이터를 읽는 기능을 제공한다.
- FileOutputStream 클래스는 파일에 바이트 데이터를 쓰는 기능을 제공한다.
- 파일의 입출력 작업에 사용되며, 바이트 스트림의 일종이다.
(3) DataInputStream / DataOutputStream:
- DataInputStream 클래스는 원시 데이터 타입(정수, 실수 등)을 읽는 기능을 제공한다.
- DataOutputStream 클래스는 원시 데이터 타입을 쓰는 기능을 제공한다.
- 이진 데이터를 처리할 때, 데이터의 타입 정보를 유지하면서 읽고 쓸 수 있다.
(4) BufferedInputStream / BufferedOutputStream
- BufferedInputStream 클래스는 다른 입력 스트림으로부터 데이터를 버퍼링하여 읽는 기능을 제공한다.
- BufferedOutputStream 클래스는 다른 출력 스트림에 데이터를 버퍼링하여 쓰는 기능을 제공한다.
- 입출력의 효율을 높이기 위해 사용되며, 내부적으로 버퍼링하여 데이터를 한 번에 읽거나 쓰게 된다.
3) 문자 스트림(Character Stream)
문자 스트림은 데이터를 문자 단위로 처리하는 스트림이다. Reader와 Writer 클래스를 기반으로 하며, FileReader, FileWriter, BufferedReader, BufferedWriter 등의 구체적인 클래스를 사용하여 텍스트 기반의 데이터 소스와 대상을 처리할 수 있다. 문자 스트림은 텍스트 인코딩 및 디코딩을 자동으로 처리하여 문자의 인코딩 문제를 덜 신경쓰고 텍스트 데이터를 다룰 수 있도록 도와준다. 때문에 문자 스트림은 주로, 텍스트 데이터를 처리할 때 사용한다.
예시 코드>
// 문자 스트림을 이용한 텍스트 파일 읽기 및 쓰기
try (Reader reader = new FileReader("text.txt")) {
int charData;
while ((charData = reader.read()) != -1) {
System.out.print((char) charData);
}
} catch (IOException e) {
e.printStackTrace();
}
try (Writer writer = new FileWriter("output.txt")) {
String text = "Hello, world!";
writer.write(text);
} catch (IOException e) {
e.printStackTrace();
}
4) 문자 스트림 관련 클래스
(1) Reader / Writer
- Reader 클래스는 텍스트 데이터를 문자 단위로 읽어오는 추상 클래스이다.
- Writer 클래스는 텍스트 데이터를 문자 단위로 쓰는 추상 클래스이다.
- 문자 스트림을 처리하는 기본적인 추상화를 제공하며, 텍스트 데이터의 인코딩 처리를 수행한다.
(2) FileReader / FileWriter
- FileReader 클래스는 파일로부터 문자를 읽어오는 클래스이다.
- FileWriter 클래스는 파일에 문자를 쓰는 클래스이다.
- Reader / Writer를 확장한 클래스로, 파일에 대한 입출력을 간편하게 처리할 수 있다.
- 단점은 텍스트 데이터를 처리할 때 인코딩을 자동으로 처리하지 않는다는 점이다.
(3) BufferedReader / BufferedWriter
- BufferedReader 클래스는 입력 스트림으로부터 텍스트를 버퍼링하여 효율적으로 읽는 클래스이다.
- BufferedWriter 클래스는 출력 스트림으로부터 버퍼를 이용해 텍스트를 효율적으로 쓰는 클래스이다.
- 버퍼링된 스트림들은 작은 데이터 덩어리를 한꺼번에 읽거나 쓰는 등의 성능 향상을 제공하며, 문자 인코딩 및 디코딩을 자동으로 처리하여 텍스트 데이터 처리를 용이하게 해준다.
'자바(Java)' 카테고리의 다른 글
Java - equals, hashCode (2) | 2024.11.08 |
---|---|
Java - 엑셀 업로드 후 DB 저장 (0) | 2023.08.25 |
Java - 메소드 참조와 Optional 클래스 (0) | 2023.08.11 |
Java - 람다식 (0) | 2023.08.11 |
Java Collection - 2 (0) | 2023.07.19 |