자바에서 파일이나 네트워크와 같은 외부 장치로 데이터를 읽고 쓰는 과정은 스트림(stream) 을 통해 일관되게 처리할 수 있습니다. 이 글에서는 입력 스트림의 동작 원리와 버퍼를 활용하여 성능을 최적화하는 방법에 대해 살펴보겠습니다.


1. 입력 스트림과 버퍼의 기본 원리

자바에서 read() 메소드를 사용할 때, 미리 생성된 byte[] 버퍼를 전달하여 한 번에 여러 바이트를 읽어올 수 있습니다.

  • byte[] 버퍼: 데이터를 임시로 저장하는 공간입니다.
  • offset: 버퍼에서 데이터를 기록하기 시작할 인덱스입니다.
  • length: 읽어올 최대 바이트 수를 지정합니다.

현대 컴퓨터는 바이트 단위로 데이터를 주고받으며, 스트림을 사용하면 파일 I/O, 네트워크 통신 등 다양한 데이터 전송 방식을 동일한 방식으로 다룰 수 있습니다.

참고: read()write() 메소드가 호출될 때마다 OS의 시스템 콜이 발생하는데, 시스템 콜은 상대적으로 무거운 작업입니다. HDD나 SDD와 같은 저장 장치에서도 한 바이트씩 처리하면 성능에 큰 영향을 주므로, 버퍼를 활용하여 여러 바이트를 한 번에 처리하는 것이 효율적입니다.


2. 버퍼 사용의 중요성

2.1 버퍼의 역할

  • 시스템 콜 최소화:
    한 번에 많은 데이터를 전달하면 시스템 콜 호출 횟수가 줄어들어 성능이 향상됩니다.
  • 하드웨어 최적화:
    실제 디스크나 파일 시스템은 보통 4KB 또는 8KB 단위로 데이터를 읽고 쓰므로, 이보다 작은 단위로 데이터를 다루면 불필요한 오버헤드가 발생합니다.

2.2 BufferedXXX 클래스

자바에서는 기본 스트림(예: FileOutputStream, FileInputStream) 외에도, 보조 스트림으로서 BufferedOutputStreamBufferedInputStream을 제공하여 버퍼 기능을 쉽게 사용할 수 있습니다.

  • BufferedOutputStream:
    내부적으로 버퍼를 사용하여 데이터를 모았다가 한 번에 출력 스트림에 전달합니다.

    주의: 반드시 대상 스트림(예: FileOutputStream)이 필요합니다.

  • BufferedInputStream:
    미리 정해진 버퍼 크기만큼 데이터를 읽어와 저장해두고, 이후 read() 호출 시 버퍼에서 데이터를 반환하여 성능을 개선합니다.

3. 성능 최적화 전략

  • 작은 파일 처리:
    파일의 크기가 작아 메모리 부담이 적다면, 전체 파일을 한 번에 처리하는 것도 좋은 선택입니다.
  • 큰 파일 또는 성능이 중요한 경우:
    파일이 크거나 성능이 중요한 경우, 버퍼 크기를 적절히 조절하여 파일을 나누어 처리하는 것이 좋습니다.
  • 동기화 비용 고려:
    BufferedXXX 클래스는 내부에 동기화 코드를 포함하고 있으므로, 멀티스레드 환경에서의 안전성을 제공하지만 단일 스레드 환경에서는 오버헤드가 발생할 수 있습니다.

4. 자바 코드 예제

다음 예제는 BufferedInputStreamBufferedOutputStream을 사용하여 파일을 읽고 쓰는 과정을 보여줍니다.

import java.io.*;

public class BufferedFileCopy {
    public static void main(String[] args) {
        // 원본 파일과 대상 파일 경로 설정
        String inputFile = "input.txt";
        String outputFile = "output.txt";
        
        // 4KB 크기의 버퍼 사용 (파일 시스템의 기본 단위와 유사)
        byte[] buffer = new byte[4096];
        
        try (
            // 기본 스트림에 보조 스트림(버퍼 기능)을 추가
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFile));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))
        ) {
            int bytesRead;
            // 버퍼에 데이터를 읽어오면서 파일 전체를 복사
            while ((bytesRead = bis.read(buffer, 0, buffer.length)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            // 버퍼에 남아있는 데이터를 출력 스트림에 완전히 전달
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
profile
배움을 추구하는 개발자

0개의 댓글