CodeGym /행동 /JAVA 25 SELF /이진 파일 읽기와 쓰기: InputStream, OutputStream

이진 파일 읽기와 쓰기: InputStream, OutputStream

JAVA 25 SELF
레벨 36, 레슨 2
사용 가능

1. 소개

가장 기본적인 것부터 시작해 봅시다. 이진 파일은 텍스트 파일과 무엇이 다를까요? 텍스트 파일은 일반 메모장에서 열어 문자, 숫자, 공백 등 기호를 눈으로 확인할 수 있는 파일입니다. 예를 들어 my_notes.txt 또는 poem.txt가 있습니다.

이진 파일은 텍스트가 아니라 임의의 바이트를 담는 파일입니다. 이미지(.jpg, .png), 음악(.mp3), 압축 파일(.zip), 실행 파일(.exe), 동영상(.mp4), 데이터베이스 파일 등일 수 있습니다. 이런 파일을 메모장에서 열면 ÿØÿà 같은 내용이나 이해할 수 없는 기호들의 벽이 보일 것입니다. 정상입니다! 컴퓨터는 바이트만 ‘이해’합니다 — 컴퓨터에게 텍스트, 이미지, 동영상은 모두 바이트의 연속일 뿐입니다. 텍스트 파일에서는 그 바이트를 문자로 해석할 수 있지만, 이진 파일에서는 사람이 읽도록 만들어지지 않은 ‘원시’ 데이터입니다.

이진 파일을 다루는 기본 클래스

Java에서는 이진 파일을 다루기 위해 바이트 스트림을 사용합니다:

  • InputStream — 바이트를 읽는 기본 클래스.
  • OutputStream — 바이트를 쓰는 기본 클래스.

파일을 대상으로 하는 구체 구현이 있습니다:

  • FileInputStream — 파일에서 바이트를 읽습니다.
  • FileOutputStream — 파일에 바이트를 씁니다.

FileReader와 FileWriter를 들어본 적이 있다면, 이들은 문자 기반으로 동작하며 텍스트에만 적합하다는 점을 기억하세요. 이진 파일에는 반드시 InputStream/OutputStream 과 그 하위 클래스를 사용하세요.

2. 이진 파일 읽기

바이트 단위로 읽기

가장 단순한 방법은 파일을 한 번에 1바이트씩 읽는 것입니다. 이해하기 쉽지만 매우 느립니다.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b는 0부터 255까지의 값(바이트)이며, -1은 파일의 끝을 의미합니다
        // 바이트를 처리할 수 있습니다. 예를 들어 모든 바이트의 합을 계산할 수 있습니다
    }
}

메서드 read()는 다음 바이트를 int(0부터 255)로 반환하고, 파일이 끝나면 -1을 반환합니다. 보통은 매우 특수한 경우(예: 파일 구조를 분석할 때)가 아니면 1바이트씩 읽지 않습니다.

블록 단위(버퍼 사용)로 읽기

1바이트씩 읽는 것은 사과를 하나씩 사러 상점에 여러 번 가는 것과 같습니다. 한 번에 봉지째로 가져오는 것이 훨씬 효율적이죠! Java에는 파일에서 바이트 배열을 채워 주는 메서드 read(byte[] buffer)가 있습니다.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // 4 KB 버퍼
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer에는 파일에서 읽은 bytesRead 바이트가 들어 있습니다
        // 이 바이트들을 처리할 수 있습니다. 예를 들어 다른 곳에 저장할 수 있습니다
    }
}

메서드 read(buffer)는 실제로 읽은 바이트 수를 반환합니다(특히 마지막 읽기에서는 버퍼 크기보다 적을 수 있습니다). 이 방식은 디스크 접근 횟수가 줄어 더 빠릅니다.

예시: 파일 복사

어떤 이진 파일이든(예: 이미지) 한 위치에서 다른 위치로 복사하는 간단한 프로그램을 작성해 봅시다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 8 KB — 대부분의 작업에 적합한 크기
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("복사가 완료되었습니다!");
        } catch (IOException e) {
            System.out.println("복사 중 오류: " + e.getMessage());
        }
    }
}

간단합니다. 원본 파일에서 블록을 읽어 바로 새 파일에 씁니다. 이 방법은 이미지, 압축 파일, 동영상 등 어떤 파일에도 동작합니다.

3. 이진 파일 쓰기

바이트 배열 쓰기

네트워크에서 받았거나 프로그램에서 생성한 바이트 배열이 있다면, 다음과 같이 파일에 쓸 수 있습니다:

byte[] data = new byte[] {1, 2, 3, 4, 5}; // 예시 배열

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // 배열 전체를 파일에 씁니다
}

메서드 write(byte[])는 배열의 모든 바이트를 기록합니다. 배열의 일부만 기록할 수도 있습니다: out.write(data, offset, length).

부분적으로 파일 쓰기(예: 복사 시)

읽기 때와 마찬가지로 보통 버퍼를 사용합니다:

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

여기서는 파일에서 읽은 내용을 즉시 다른 파일에 씁니다. 이런 코드는 압축 프로그램, 다운로드 도구, 이미지 처리기 등에서 자주 볼 수 있습니다.

4. 유용한 팁과 주의점

왜 이진 파일에 Reader/Writer를 쓰면 안 될까요?

Reader와 Writer는 바이트가 아니라 문자(char)로 동작합니다. 이들은 인코딩(예: UTF-8)에 따라 바이트를 문자로 자동 변환합니다. 텍스트에는 편리하지만, 이진 파일에는 치명적입니다!

FileWriter로 그림을 기록하려고 하면 열 수 없는 손상된 파일이 됩니다. 기억하세요: 텍스트가 아닌 어떤 파일에도 InputStream/OutputStream 만 사용하세요!

이진 파일 작업의 핵심 차이점과 팁

  • 버퍼 크기: 버퍼가 너무 작으면(디스크 접근이 너무 많아져) 느려지고, 너무 크면 불필요한 메모리를 차지합니다. 4–16 KB가 보통 적절합니다.
  • 오류 처리: 항상 IOException을 처리하세요 — 파일이 없을 수도, 잠겨 있을 수도, 디스크 공간이 부족할 수도 있습니다.
  • 스트림 닫기: try-with-resources를 사용하세요 — 오류가 나도 파일이 확실히 닫히도록 보장합니다.
  • 파일 덮어쓰기: new FileOutputStream("file.bin")으로 열면 파일이 덮어써집니다. 끝에 이어 쓰려면 append = true 생성자를 사용하세요.
  • 권한: 프로그램이 파일을 열지 못한다면 읽기/쓰기 권한을 확인하세요.
  • readAllBytes(): 한 줄로 파일 전체를 바이트 배열로 읽을 수 있습니다. 큰 파일에는 메모리를 모두 소모할 수 있으니 사용하지 마세요!

5. 이진 파일 작업에서 흔한 실수

오류 №1: 이진 파일에 FileReader/FileWriter를 사용하는 경우. 이러한 클래스는 바이트를 문자로(그리고 그 반대로) 변환하므로 데이터가 손상됩니다. 이미지, 압축 파일 등에는 치명적입니다.

오류 №2: read()의 반환값을 무시함. 메서드 read(byte[])는 요청한 크기보다 적게 읽을 수 있으며, 특히 마지막 블록에서 그렇습니다. 실제로 처리한 바이트 수를 알기 위해 반환값을 항상 사용하세요.

오류 №3: 스트림을 닫지 않음. 파일을 닫지 않으면 잠긴 상태가 되거나 데이터가 끝까지 기록되지 않을 수 있습니다(특히 쓰기 시!). try-with-resources를 사용하세요.

오류 №4: 파일 크기를 고려하지 않고 전체를 메모리로 읽으려 함. 큰 파일에서는 OutOfMemoryError가 발생할 수 있습니다. 버퍼를 사용해 나누어 읽으세요.

오류 №5: 예외를 처리하지 않음. 파일 작업에서는 언제나 오류가 발생할 수 있습니다. 예: 파일 없음, 권한 부족, 디스크 가득 참. IOException을 반드시 처리하세요.

1
과제
JAVA 25 SELF, 레벨 36, 레슨 2
잠금
이진 스트림에서 귀중한 조각 추출 🧪
이진 스트림에서 귀중한 조각 추출 🧪
1
과제
JAVA 25 SELF, 레벨 36, 레슨 2
잠금
유물의 에너지 잠재력 계산 ⚛️
유물의 에너지 잠재력 계산 ⚛️
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION