스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
1) 스트림 (Stream)
데이터가 들어온 순서대로 흘러다니는 단방향의 통로를 말한다.
입력 스트림과 출력 스트림이 존재한다.
입력 스트림 : 데이터를 읽어오는 통로를 말한다. 그리고 큐와 같은 FIFO 구조로 받아온다.
출력 스트림 : 데이터를 보내는 통로를 말한다.
스트림을 통해 기본적으로 byte 또는 byte[] 형태로 흘러다닌다.
그리고 동기적, blocking 방식으로 동작한다. 그래서 읽거나 쓰기 위해 스트림에 요청을 하게 되면 자신의 역할에 맞춰 다시 데이터를 읽거나 쓸 수 있을 때까지 다른 작업을 하지 못하고 무한대기한다.
또한 사용 후 닫아주지 않을 경우 메모리 누수가 발생하여 예외처리를 해줘야 한다.
// 바이트 기반
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
String filePath = "a.txt";
// 바이트 기반 문자 쓰기
try(FileOutputStream f = new FileOutputStream(filePath)){
f.write("Hello Java".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//바이트 기반 문자 읽어오기
try(FileInputStream fin = new FileInputStream(filePath)){
int data;
while((data= fin.read())!=-1){
System.out.print((char) data); // 바이트 -> 문자
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//문자 기반 스트림 ( FileWriter , FileReader)
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
String filePath = "b.txt";
try(FileWriter fw = new FileWriter(filePath)){
fw.write("hello java~!!");
}catch(IOException e ){
e.printStackTrace();
}
try(FileReader fr = new FileReader(filePath)){
int data;
while((data = fr.read())!=-1){
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2) buffer
임시로 데이터를 담아둘 수 있는 queue이다.
바이트 단위의 데이터가 입력될 때마다 Stream 은 즉시 전송하는데 이는 디스크 접근이나 네트워크 접근과 같은 오버헤드 발생 우려가 있어 비효율적이다.
buffer 는 중간에서 입력을 모아 한 번에 출력함으로써 I/O의 성능을 향상시킨다.
-일반 출력과 버퍼 출력의 시간 차이로 성능 확인해보기-
import java.sql.SQLOutput;
public class Main {
public static void main(String[] args) {
long starttime = System.currentTimeMillis();
for(int i=0;i<100000;i++){
System.out.println(i);
}
System.out.println();
long finishtime = System.currentTimeMillis();
long start = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
for(int i =0;i<100000;i++){
sb.append(i).append('\n');
}
System.out.println(sb);
long finish = System.currentTimeMillis();
System.out.println("========< 수행 시간 비교하기 >============");
System.out.printf("일반 출력의 수행 시간 : %dms\n", finishtime- starttime);
System.out.printf("버퍼를 이용한 수행 시간 : %dms\n", finish - start);
}
}
- 수행 결과 -
이처럼 일반 출력보다 버퍼로 중간에 결과들을 모았다가 한 번에 출력하는 것이 나음을 볼 수 있다.
- 버퍼를 기반으로 한 I/O 입출력
// 바이트 기반 BufferedOuputStream , BufferedInputStream
import java.io.*;
public class Main {
public static void main(String[] args) {
String path = "c.txt";
try(BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(path))){
bo.write("hello java world!!".getBytes());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
try(BufferedInputStream be = new BufferedInputStream(new FileInputStream(path))){
int data;
while( (data= be.read())!= -1){
System.out.println(data);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// 문자 기반 BufferedWriter / BufferedReader
import java.io.*;
public class Main {
public static void main(String[] args) {
String path = "d.txt";
try(BufferedWriter bw = new BufferedWriter(new FileWriter(path))){
bw.write("hello i'm java~~");
} catch (IOException e) {
throw new RuntimeException(e);
}
try(BufferedReader br = new BufferedReader(new FileReader(path))){
int data;
while((data = br.read())!=-1){
System.out.print((char)data);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3) 채널 (Channel)
자바의 기본 입출력 방식인 Stream은 입출력 속도가 많이 느리다. 이를 해결하기 위해 NIO가 java.nio 패키지에 포함되어 등장했는데 Channel 이 NIO 기본 입출력 방식이다.
이는 데이터가 흘러다니는 양방향의 통로로 input과 output을 구분하지 않는다. 또한 Stream은 입출력을 위해 InputStream과 OutputStream을 만들어야 했지만 Channel은 그럴 필요가 없다. 기본적으로 Buffer을 통해서만 read와 write를 할 수 있는 buffer 방식이고 blocking과 non-blocking 방식 모두 가능하다. NIO 는 Non-blocking 방식으로 데이터를 처리할 수 있어 과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용 할 수 있다.
ex)
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Main {
public static void main(String[] args) {
Path path = Paths.get("a.txt");
try(FileChannel ch = FileChannel.open(path, StandardOpenOption.READ)){
ByteBuffer buffer = ByteBuffer.allocate(1024);
ch.read(buffer);
buffer.flip();
Charset c = Charset.defaultCharset();
String inputData = c.decode(buffer).toString();
System.out.println("inputDate: "+inputData);
buffer.clear();
}catch(Exception e ){
e.printStackTrace();
System.out.println("파일 작업 실패");
}
}
}
InputStream 과 OutputStream
1) InputStream
데이터를 읽어오는 입구와 같은 곳이다.
2) OutputStream
데이터를 wrtie하는 출구이다.
위 두 개는 Stream을 이해하는 개념적인 의미도 있지만 Java에서는 바이트 시퀀스를 읽거나 쓰기위한 기본 기능을 정의 해놓은 추상 클래스이다. 즉 Java의 모든 바이트 스트림을 InputStream , OutputStream 을 위해 빌드된다. 스트림 클래스들은 다형성을 이용해서 스트림 계층화가 가능하며, 더 큰 데이터 유형 처리와 같은 더 높은 수준의 기능을 제공할 수 있다.
Byte와 Character 스트림
https://docs.oracle.com/javase/8/docs/api/index.html
Java Platform SE 8
docs.oracle.com
1) Byte 스트림
1byte 단위로 읽어들인다.
바이트 스트림의 읽기 담당인 InputStream 을 살펴보자.
메소드로는 read() , read(byte[] b) 등이 있는 것을 볼 수 있다. 대표적으로 이 둘에 대해 살펴보면 다음과 같다.
read() 는 스트림에서 1 byte 데이터만 읽어온다. 그리고 int 형으로 return 한다.
그리고 데이터를 byte[] 배열 안에 저장 가능하다. 다시 말해 매개변수로 받은 바이트 배열(byte[])에 해당 바이트 배열에 스트림으로 전달 받은 데이터를 저장한다. 그렇게 되면 배열을 통해 스트림으로 전달 받은 데이터를 사용할 수 있다.
2) char 스트림
문자 스트림의 경우 2byte 단위로 전달받으므로 char 배열(char[])을 사용한다.
read 메소드를 보면 매개변수로 받은 char 배열 스트림으로 받은 데이터를 저장한다. 그러면 char[] 배열을 통해 스트림으로 전달받은 데이터를 사용할 수 있다.
즉, 1byte 단위로 읽어들인 데이터는 byte[] 배열 혹은 byte 변수에 저장 가능하고, 2byte 단위로 읽어들인 데이터는 char[] 배열 혹은 char 변수에 저장 가능하다.
표준 스트림 ( System.in, System.out. System.err)
System은 JVM 을 구성하고 있는 표준 장치를 뜻하는 클래스로 이 클래스에 정적 멤버 변수로 선언되어있는 스트림인 in, out, err를 표준 스트림이라고 한다.
1) System.out
System.out.println()에서 println()은 out 객체 클래스인 PrintStream 클래스의 메소드이다. PrintStream은 OuputStrema을 상속하여 메소드들의 예외처리를 한 클래스 이다.
2) System.err
out과 마찬가지로 PrintStream 타입이며 표준 에러 출력 장치를 의미한다. err는 별도의 설정이 없는 한 기본적으로 모니터를 출력 장치로 한다.
3) System.in
이는 일반적으로 콘솔 프로그램의 키보드 입력에 연결되는 InputStream 이다. 명령줄에서 Java 애플리케이션을 시작하고 CLI 콘솔에 포커스가 있는 동안 키보드에서 입력하면 키보드 입력은 일반적으로 해당 Java 애플리케이션 내부에서 System.in을 통해 읽을 수 있다.
파일 읽고 쓰기
1) FileWrtier , FileReader
import java.io.*;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
String file = "a.txt";
try(FileWriter fw = new FileWriter(file)){
fw.write("a b c d e f g h");
fw.close();
}catch(IOException e ) {
e.printStackTrace();
}
try(FileReader fr = new FileReader(file)){
int data = 0;
while((data = fr.read())!= -1){
System.out.print((char)data);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2) FileOuputStream , FileInputStream
import java.io.*;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
String file = "a.txt";
try(FileOutputStream os= new FileOutputStream(file)){
os.write("a b c d e f g h".getBytes());
os.close();
}catch(IOException e ) {
e.printStackTrace();
}
try(FileInputStream is = new FileInputStream(file)){
int data = 0;
while((data = is.read())!= -1){
System.out.print(data+" ");
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3) BufferedOutputStream, BufferedInputStream
import java.io.*;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
String file = "a.txt";
try(BufferedOutputStream os= new BufferedOutputStream(new FileOutputStream(file))){
os.write("a b c d e f g h".getBytes());
os.close();
}catch(IOException e ) {
e.printStackTrace();
}
try(BufferedInputStream is = new BufferedInputStream(new FileInputStream(file))){
int data = 0;
while((data = is.read())!= -1){
System.out.print(data+" ");
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4) BufferedReader, BufferedWriter
import java.io.*;
import java.nio.ByteBuffer;
public class Main {
public static void main(String[] args) {
String file = "a.txt";
try(BufferedWriter bw= new BufferedWriter(new FileWriter(file))){
bw.write("a b c d e f g h");
bw.close();
}catch(IOException e ) {
e.printStackTrace();
}
try(BufferedReader is = new BufferedReader(new FileReader(file))){
int data = 0;
while((data = is.read())!= -1){
System.out.print((char)data);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
[참고자료]
https://lordofkangs.tistory.com/84
[ JAVA ] 바이트 스트림 vs 문자 스트림
[ JAVA ] InputStream, OutputStream 프로그램 실행은 보조기억장치(Disk)에 저장된 프로그램이 RAM에 할당됨을 의미한다. 이때 할당된 RAM 영역을 System(시스템)이라 부른다. 시스템은 안팎으로 상호작용한다
lordofkangs.tistory.com
https://velog.io/@sa1341/Java-NIO-%EC%B1%84%EB%84%90Channel
Java NIO 채널(Channel)
이전에 올렸던 java.io 패키지에 대해서 공부하고 포스팅했지만, Java 4부터 등장한 java.nio에 대해서도 궁금하여 포스팅하였습니다.NIO는 의미만 봤을 때 Non-blocking IO의 줄임말이라고 생각했지만, 사
velog.io
https://javanitto.tistory.com/11
[ Java ] Java의 I/O (feat. Stream, Buffer, Channel)
목차 스트림(Stream), 버퍼(Buffer), 채널(Channel) 기반의 I/O InputStream, OutputStream Byte와 Character 스트림 표준스트림(System.in, System.out, System.err) 파일 읽고 쓰기 개요 I/O란 Input/Output으로 데이터를 입력하고
javanitto.tistory.com
https://jenkov.com/tutorials/java-io/system-in-out-error.html
https://developingman.tistory.com/59
[JAVA] 파일 입출력 (I/O)
InputStream, OutputStream 자바에서는 입출력을 다루기 위한 InputStream, OutputStream을 제공합니다. Stream은 단방향으로만 데이터를 전송할 수 있기에, 입력과 출력을 동시에 처리하기 위해서는 각각의 스
developingman.tistory.com
'🦁아기사자' 카테고리의 다른 글
[Java] 람다식 (0) | 2025.04.01 |
---|---|
[Java] 제네릭 (2) | 2025.03.31 |
[Java] 애노테이션 (0) | 2025.03.27 |
[Java] Enum (0) | 2025.03.26 |
[Java] 멀티스레드 프로그래밍 (0) | 2025.03.25 |