본문 바로가기
JAVA

[자바] I/O 스트림

by 감자b 2024. 12. 25.

입출력 스트림은 데이터를 읽고 쓰는 것을 추상화 한 것으로 자바 프로그램이 외부로부터 데이터를 읽거나 내보내기 위한 통로 역할을 한다.

  • 입력 스트림 : 외부에서 데이터를 읽는데 사용
  • 출력 스트림 : 자바 내부의 데이터를 외부로 내보내는데 사용

자바 I/O 스트림 구조


스트림 주요 메서드

InputStream

  • int read() : 하나의 바이트를 읽고, 읽은 바이트의 정수 값을 반환. 더 이상 읽을 바이트가 없으면 -1
  • int read(byte[] b) : 바이트 배열에 데이터를 읽어 저장. 읽은 바이트 수를 반환하며, 더 이상 읽을 바이트가 없으면 -1
  • int read(byte[] b, int off, int len):
    • 바이트 배열의 특정 오프셋에서 시작하여 최대 len 바이트를 읽음.
    • 읽은 바이트 수를 반환, 더 이상 읽을 바이트가 없으면 -1
  • void close() : 스트림을 닫아 리소스를 해제.
  • int available() : 현재 읽을 수 있는 바이트 수를 반환

 

OutputStream

  • void write(int b) : 하나의 바이트를 스트림에 쓴다.
  • void write(byte[] b) : 바이트 배열의 모든 데이터를 스트림에 쓴다.
  • void write(byte[] b, int off, int len) : 바이트 배열의 특정 오프셋에서 시작하여 최대 len 바이트를 스트림에 쓴다.
  • void flush() : 출력 버퍼에 남아 있는 모든 데이터를 강제로 스트림에 쓴다.
  • void close() : 스트림을 닫아 리소스를 해제.

위에서 read, write 메서드 모두 버퍼라는 단위로 입출력을 할 수 있도록 지원하는데 이러한 이유는 무엇일까?

I/O 스트림의 입출력 메서드는 호출할 때 마다 OS의 시스템 콜을 통해 명령어를 전달하는데 이는 상대적으로 무거운 작업이다.

 

시스템 콜(System Call)

시스템 콜(System call)이란?응용 프로그램 요청에 따라서 운영체제의 커널 서비스를 받기 위한 일종의 함수 호출을 의미한다. 즉 커널 영역의 기능을 사용자 모드에서 사용 가능하게 하며, 프로세

hbb-devlog.tistory.com

 

따라서 시스템 콜에 의한 성능 저하가 일어나므로 버퍼에 담아 한 번에 데이터를 읽고 쓸 수 있도록 지원한다.

저장 장치(디스크나 파일 시스템)에서 보통 해당 4KB, 8KB 블록 단위로 나누어 저장하기 때문에 한 번에 많이 쓰더라도 한계가 있다. 따라서 버퍼의 크기 역시 비슷한 4KB, 8KB로 잡는 것이 효율적이다.

public class StreamTestMain {

    public static final String FILE_NAME = "temp/test.dat";
    public static final int BUFFER_SIZE = 10;
    public static final int FILE_SIZE = 55;

    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(FILE_NAME);
        BufferedOutputStream bos = new BufferedOutputStream(fos, BUFFER_SIZE);
        for (int i = 0; i < FILE_SIZE; i++) {
            bos.write("A".getBytes(StandardCharsets.UTF_8));
        }
        bos.close();

        FileInputStream fis = new FileInputStream(FILE_NAME);
        BufferedInputStream bis = new BufferedInputStream(fis, BUFFER_SIZE);
        StringBuilder sb = new StringBuilder();
        int data;
        while ((data = bis.read()) != -1) {
            sb.append((char) data);
        }
        System.out.println(sb);
        bis.close();
    }
}

여기서 FileOutputStream과 같이 단독으로 사용할 수 있는 스트림을 기본 스트림이라고 하며, BufferedOutputStream과 같이 인자로 다른 스트림이 필요한 경우를 보조 스트림이라고 한다.

BufferedStream의 경우 버퍼를 쉽게 다룰 수 있도록 기능이 지원되며 해당 스트림의 경우 내부에서 동기화를 적용하여 입출력을 진행한다. 그리고 버퍼가 남아있는 상태에서 close()를 호출하면 자동으로 flush()를 해주어 사용자가 편리하게 사용할 수 있도록 지원한다.

그리고 보조 스트림의 close() 메서드를 호출하면, 내부적으로 기본 스트림의 close() 메서드도 된다.

 

만약 입출력 시에 데이터의 크기가 크지 않다면 한 번에 버퍼에 담고 입출력하는 방법도 있다.

시스템 콜이 한 번 일어나므로

public class StreamTestMain {

    public static final String FILE_NAME = "temp/test.dat";
    public static final int FILE_SIZE = 55;

    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(FILE_NAME);
        byte[] buffer = new byte[FILE_SIZE];
        Arrays.fill(buffer, (byte) 'A');
        fos.write(buffer);
        fos.close();

        FileInputStream fis = new FileInputStream(FILE_NAME);
        byte[] bytes = fis.readAllBytes(); // 한 번에 읽음
        String data = new String(bytes, StandardCharsets.UTF_8);
        System.out.println(data);
        fis.close();
    }
}

Reader, Writer

위 코드를 보면 쓸 때는 바이트로 변환해야하고, 읽을 때는 읽어온 바이트를 다시 문자로 변환해야 하는 번거로움이 있었다.

자바는 바이트 기반의 입출력 스트림을 제외하고 문자 입출력을 위한 스트림을 제공하는데 이것이 Reader, Writer이다.

(문자 입출력을 위한 기능을 제공한다고 해서 바로 문자를 입출력하는 것이 아니라 내부에서 문자 집합을 이용해 인코딩, 디코딩을 해주는 것)

Reader/Writer 구조

따라서 위 코드는 다음과 같이 바꿀 수 있다.

Reader, Writer 내부에서 생성자에 넘겨주었던 문자 집합을 가지고 입출력 데이터를 인코딩, 디코딩을 자동으로 해준다.

public class ReaderTestMain {

    public static final String FILE_NAME = "temp/test.dat";
    public static final int FILE_SIZE = 55;

    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter(FILE_NAME, StandardCharsets.UTF_8);
        BufferedWriter bw = new BufferedWriter(fw);
        for (int i = 0; i < FILE_SIZE; i++) {
            bw.write("A");
        }
        bw.close();

        FileReader fr = new FileReader(FILE_NAME, StandardCharsets.UTF_8);
        BufferedReader br = new BufferedReader(fr);
        StringBuilder sb = new StringBuilder();
        int data;
        while ((data = br.read()) != -1) {
            sb.append((char) data);
        }
        System.out.println(sb);
        br.close();
    }
}

 

'JAVA' 카테고리의 다른 글

[자바] 리플렉션  (0) 2024.12.25
[자바] 소켓 프로그래밍  (0) 2024.12.25
[자바] 스레드 풀, Executor  (0) 2024.12.25
[자바] 동시성 컬렉션  (0) 2024.12.25
[자바] 원자적 연산, 동기화  (0) 2024.12.25