Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags more
Archives
Today
Total
관리 메뉴

프로그래밍 공부

[자바] #24. I/O 스트림 본문

자바

[자바] #24. I/O 스트림

하 냥 2025. 3. 1. 05:33

🔍 I/O 스트림의 분류

1. 데이터 단위에 따른 분류

  1. 바이트 스트림(Byte Stream)
    • 1바이트(8비트) 단위로 데이터 처리
    • 입력: InputStream과 그 하위 클래스
    • 출력: OutputStream과 그 하위 클래스
    • 예시 클래스: FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
  2. 문자 스트림(Character Stream)
    • 2바이트(16비트, UTF-16) 단위로 문자 처리
    • 입력: Reader와 그 하위 클래스
    • 출력: Writer와 그 하위 클래스
    • 예시 클래스: FileReader, FileWriter, BufferedReader, BufferedWriter

2. 기능에 따른 분류

  • 기본 스트림: 실제 데이터를 읽고 씀
    • FileInputStream, FileOutputStream, FileReader, FileWriter
  • 보조 스트림: 기본 스트림에 기능을 추가 (버퍼링, 필터링, 변환 등)
    • BufferedReader, BufferedWriter, DataInputStream, DataOutputStream, ObjectInputStream, ObjectOutputStream

 

💫 바이트 스트림 (Byte Stream)

입력 스트림 (InputStream) 주요 메서드

  • int read(): 1바이트 읽기 (더 이상 읽을 데이터가 없으면 -1 반환)
  • int read(byte[] b): 바이트 배열에 읽기
  • void close(): 스트림 닫기

예제: FileInputStream 사용

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

public class ByteInputExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int data;
            while ((data = fis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

출력 스트림 (OutputStream) 주요 메서드

  • void write(int b): 1바이트 쓰기
  • void write(byte[] b): 바이트 배열 쓰기
  • void flush(): 버퍼 비우기
  • void close(): 스트림 닫기

예제: FileOutputStream 사용

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

public class ByteOutputExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            String data = "Hello, Java I/O!";
            fos.write(data.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

문자 스트림 (Character Stream)

입력 스트림 (Reader)

  • int read(): 1문자 읽기
  • int read(char[] cbuf): 문자 배열 읽기
  • void close(): 스트림 닫기

예제: FileReader 사용

import java.io.FileReader;
import java.io.IOException;

public class CharInputExample {
    public static void main(String[] args) {
        try (FileReader fr = new FileReader("example.txt")) {
            int data;
            while ((data = fr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

출력 스트림 (Writer)

  • void write(int c): 문자 쓰기
  • void write(char[] cbuf): 문자 배열 쓰기
  • void write(String str): 문자열 쓰기
  • void flush(): 버퍼 비우기
  • void close(): 스트림 닫기

예제: FileWriter 사용

import java.io.FileWriter;
import java.io.IOException;

public class CharOutputExample {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("output.txt")) {
            fw.write("자바 I/O 문자 스트림 예제입니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🚀 보조 스트림 (Buffered, Data, Object 스트림)

버퍼 스트림 (Buffered Stream)

  • 성능 향상을 위해 버퍼 사용
  • 주요 클래스: BufferedReader, BufferedWriter, BufferedInputStream, BufferedOutputStream

예제: BufferedReader 사용

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

데이터 스트림 (Data Stream)

  • 자바 기본 데이터 타입(int, double, boolean 등)을 읽고 쓸 수 있음
  • 클래스: DataInputStream, DataOutputStream

예제: DataOutputStream & DataInputStream

import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) throws IOException {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"))) {
            dos.writeInt(42);
            dos.writeDouble(3.14159);
            dos.writeBoolean(true);
        }

        try (DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {
            System.out.println(dis.readInt());
            System.out.println(dis.readDouble());
            System.out.println(dis.readBoolean());
        }
    }
}

객체 스트림 (Object Stream)

  • 객체 직렬화(Serialization) 및 역직렬화(Deserialization) 지원
  • 클래스: ObjectOutputStream, ObjectInputStream

예제: 직렬화와 역직렬화

import java.io.*;

class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class ObjectStreamExample {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student("홍길동", 21);

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.obj"))) {
            oos.writeObject(student);
        }

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.obj"))) {
            Student s = (Student) ois.readObject();
            System.out.println(s.name + " - " + s.age);
        }
    }
}

try-with-resources 문법

try-with-resources는 AutoCloseable 인터페이스를 구현한 클래스의 인스턴스를 자동으로 닫아준다.

try (FileReader fr = new FileReader("example.txt")) {
    // 리소스 사용
} catch (IOException e) {
    e.printStackTrace();
}
// 자동으로 close() 호출됨

 

 

 



 

 

 

자바에서는 데이터를 처리할 때 바이트 단위로 처리하는 바이트 스트림과 문자 단위로 처리하는 문자 스트림이 있다.

두 개의 차이를 정확히 이해하려면, 바이트와 문자의 차이를 먼저 알아야 한다.

 

🔹 바이트와 문자의 차이

✅ 바이트(Byte)란?

  • 1바이트(8비트) 크기의 데이터 단위
  • 숫자, 이미지, 오디오, 영상 등의 이진(Binary) 데이터를 처리할 때 사용됨
  • 1바이트는 0~255(2^8) 범위의 값을 가질 수 있음
  • 파일을 그대로 읽고 쓰는 경우(텍스트 파일 포함)에도 사용됨

✅ 문자(Character)란?

  • 사람이 읽을 수 있는 문자 데이터(텍스트)를 의미
  • 자바에서 문자는 유니코드(UTF-16, 2바이트 또는 4바이트)로 표현됨
  • 즉, 문자를 처리할 때는 바이트가 아니라 문자 단위(16비트, 2바이트 이상)로 다루어야 함

📌 바이트 스트림

  • 8비트(1바이트) 단위로 데이터 처리
  • 주로 이미지, 동영상, 음악 파일, 이진 파일비텍스트 데이터를 다룰 때 사용
  • 바이트 단위로 데이터를 읽고 쓰므로, 텍스트 데이터를 직접 다루면 한글, 일본어, 중국어 같은 멀티바이트 문자가 깨질 가능성이 있음
  • 주요 클래스:
    • 입력 스트림: InputStream 계열 (FileInputStream, BufferedInputStream, DataInputStream)
    • 출력 스트림: OutputStream 계열 (FileOutputStream, BufferedOutputStream, DataOutputStream)
import java.io.FileInputStream;
import java.io.IOException;

public class ByteStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int data;
            while ((data = fis.read()) != -1) { // 한 바이트씩 읽기
                System.out.print((char) data); // 바이트 데이터를 문자로 변환하여 출력
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 


📌 문자 스트림

  • 16비트(2바이트) 단위로 문자 처리
  • 텍스트 파일(문자 데이터)을 다룰 때 사용
  • Reader와 Writer 계열 클래스를 사용하여 자동으로 문자 인코딩을 처리하기 때문에, 한글 같은 멀티바이트 문자도 정상적으로 입출력 가능
  • 주요 클래스:
    • 입력 스트림: Reader 계열 (FileReader, BufferedReader)
    • 출력 스트림: Writer 계열 (FileWriter, BufferedWriter)
import java.io.FileReader;
import java.io.IOException;

public class CharStreamExample {
    public static void main(String[] args) {
        try (FileReader fr = new FileReader("example.txt")) {
            int data;
            while ((data = fr.read()) != -1) { // 한 문자씩 읽기
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

언제 바이트 스트림을 쓰고, 언제 문자 스트림을 써야 할까?

  • 바이트 스트림 (InputStream / OutputStream)을 사용해야 하는 경우
    • 이미지, 오디오, 동영상, PDF 등 이진(Binary) 데이터를 읽고 쓸 때
    • 텍스트 파일을 그대로 복사해야 하는 경우 (문자 변환 없이)
    • 네트워크 소켓을 통해 데이터를 전송할 때
  • 문자 스트림 (Reader / Writer)을 사용해야 하는 경우
    • 텍스트 파일(.txt, .csv, .xml, .json 등)을 읽고 쓸 때
    • 사람이 읽을 수 있는 문자 데이터를 처리할 때
    • UTF-8, UTF-16과 같은 멀티바이트 문자(한글, 일본어 등)를 처리할 때

 


 

📌 문자 스트림

필터 스트림은 기본 스트림(InputStream, OutputStream, Reader, Writer)에 추가적인 기능(버퍼링, 데이터 변환, 압축 등)을 제공하는 역할을 한다.

기본 스트림을 감싸서 데이터를 가공하는 기능을 추가한 스트림이다.

직접 데이터를 읽거나 쓰지는 않고, 기본 스트림을 감싸서 기능을 확장하는 역할을 한다.


🔹 필터 스트림의 종류

바이트 기반 필터 스트림 (FilterInputStream / FilterOutputStream)

클래스명 기능
BufferedInputStream / BufferedOutputStream 성능 향상을 위한 버퍼링 기능 제공
DataInputStream / DataOutputStream 기본 데이터 타입 (int, double, boolean 등) 입출력
ObjectInputStream / ObjectOutputStream 객체 직렬화(Serialization) 지원
CipherInputStream / CipherOutputStream 데이터 암호화/복호화

 

문자 기반 필터 스트림 (FilterReader / FilterWriter)

클래스명 기능
BufferedReader / BufferedWriter 성능 향상을 위한 버퍼링 기능 제공
LineNumberReader 줄 번호(Line Number)를 제공하는 스트림
PushbackReader 한 문자 되돌리기 기능 제공

 

🔽 필터 스트림 사용 예시

InputStream is = new FileInputStream("data.txt");  // 기본 바이트 스트림
BufferedInputStream bis = new BufferedInputStream(is); // 필터 스트림 추가 (버퍼링)

위 코드에서는 FileInputStream을 BufferedInputStream으로 감싸서 버퍼링 기능을 추가했다.


🔹 주요 필터 스트림 상세 설명 및 예제

1️⃣ BufferedInputStream / BufferedOutputStream

  • 바이트 스트림(InputStream / OutputStream)에 버퍼(Buffer) 기능을 추가하여 성능을 향상시킨다.
  • 작은 데이터(1바이트씩 읽기)를 한번에 여러 바이트(버퍼 크기만큼) 읽어와 처리하기 때문에 입출력 속도가 빨라진다.
import java.io.*;

public class BufferedStreamExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data.txt"))) {
            int data;
            while ((data = bis.read()) != -1) {
                System.out.print((char) data); // 파일 내용 출력
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// BufferedInputStream을 사용하면 FileInputStream보다 빠르게 데이터를 읽을 수 있다.

 

 

BufferedInputStream의 read() 메서드는 한 번에 1바이트씩 데이터를 읽는다.

하지만 내부적으로는 버퍼를 사용하여 여러 바이트를 한 번에 읽어 메모리에 저장하고, 이후에는 버퍼에서 데이터를 가져오는 방식으로 성능을 최적화한다.

 

👀 BufferedInputStream에서 read() 동작 원리

  1. 버퍼가 비어 있으면, 파일에서 한 번에 여러 바이트를 읽어와 버퍼에 채운다.
  2. 이후 read()를 호출하면, 버퍼에서 한 바이트씩 읽어서 반환한다.
  3. 버퍼에 데이터가 남아 있는 동안에는 추가적인 파일 읽기 없이 계속 버퍼에서 데이터를 꺼낸다.
  4. 버퍼가 모두 소진되면, 다시 파일에서 데이터를 읽어와 버퍼를 채우는 작업을 수행한다.
  5. 파일 끝(EOF, -1)이 나오면, while 문을 빠져나간다.

 

⚡ read() 호출 흐름 분석

✅ read()가 처음 호출될 때:

  • BufferedInputStream의 내부 버퍼가 비어있으므로, 파일에서 데이터를 읽어 버퍼를 채운다. (fill() 메서드 호출됨)
  • 이후 read()가 호출될 때마다 버퍼에서 한 바이트씩 데이터를 반환한다.

✅ read()가 여러 번 호출될 때:

  • 내부 버퍼에서 데이터를 하나씩 가져와 반환하며, 파일에서 추가적인 읽기가 발생하지 않는다.
  • 버퍼의 데이터가 모두 소진되면 다시 fill() 메서드를 호출하여 새로운 데이터를 파일에서 읽어와 버퍼에 채운다.

 

 

🎈 버퍼가 꽉 찼을 때 전송은 어떻게 이뤄지는가?

  • BufferedInputStream은 내부적으로 버퍼(byte[] buf)를 사용한다.
  • 버퍼가 비워질 때까지 메모리에서 직접 제공하는 방식으로 작동하며, 버퍼가 소진될 때만 새 데이터를 파일에서 읽어온다.
  • 데이터를 채우는 작업은 fill() 메서드에서 이루어진다.
// BufferedInputStream의 핵심 메서드 (Java 내부 구현)
private void fill() throws IOException {
    if (markpos < 0)
        pos = 0; // 버퍼 포인터를 처음으로 초기화
    count = in.read(buf, pos, buf.length - pos); // 파일에서 버퍼로 데이터 읽기
    if (count > 0) pos = 0;
}

 

버퍼가 꽉 찼을 때 전송하는 메서드는 fill().

fill()은 내부적으로 파일에서 버퍼로 데이터를 채우는 역할을 하며, read()가 처음 호출될 때 또는 버퍼가 소진되었을 때 실행된다.

 

 

🎀 예제 코드 실행 흐름 예시

파일(data.txt) 내용은 "Hello, Java!" 라고 가정하자.

 

코드 실행 흐름:

  1. BufferedInputStream 객체가 생성됨 (bis).
  2. read()가 처음 호출되면 버퍼가 비어 있으므로 fill()을 실행하여 "Hello, Java!"를 한 번에 읽어 버퍼에 저장.
  3. while 루프에서 read()를 호출할 때마다, 버퍼에서 한 바이트씩 반환 (H -> e -> l -> l -> o -> , ...).
  4. 모든 데이터가 버퍼에서 소진되면, 추가적인 데이터가 없으므로 read()는 -1을 반환하고 루프 종료.

 

결론

  • read()는 버퍼에서 한 바이트씩 반환하지만, 내부적으로 fill() 메서드를 사용하여 한 번에 데이터를 읽어온다.
  • 버퍼가 소진되었을 때만 fill()을 호출하여 파일에서 데이터를 추가로 읽어온다.
  • 버퍼 덕분에 성능이 향상되며, 매번 파일을 직접 읽지 않고 메모리에서 데이터를 제공할 수 있다.

 


 

2️⃣ DataInputStream / DataOutputStream

  • 기본 데이터 타입(int, double, boolean)을 파일에 저장하거나 읽을 때 사용한다.
  • 일반적인 FileInputStream, FileOutputStream은 바이트 단위로만 데이터를 읽고 쓰지만,
    DataInputStream, DataOutputStream을 사용하면 자바 기본 데이터 타입을 직접 저장할 수 있다.
// ✅ 예제: DataOutputStream으로 정수, 실수 저장
import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) {
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"))) {
            dos.writeInt(42);      // 정수 저장
            dos.writeDouble(3.14); // 실수 저장
            dos.writeBoolean(true); // 불리언 값 저장
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// ✅ 예제: DataInputStream으로 데이터 읽기
import java.io.*;

public class DataInputExample {
    public static void main(String[] args) {
        try (DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {
            System.out.println(dis.readInt());    // 42 출력
            System.out.println(dis.readDouble()); // 3.14 출력
            System.out.println(dis.readBoolean());// true 출력
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

💡 숫자와 논리형 데이터를 저장하고, 그대로 다시 읽을 때 유용하다!

(텍스트 파일로 저장하면 42 3.14 true 같은 문자열이 저장되는 것이 아니라, 이진(Binary) 데이터로 저장된다)


3️⃣ ObjectInputStream / ObjectOutputStream

객체를 파일에 저장(직렬화, Serialization)하고, 다시 읽어들여 복원(역직렬화, Deserialization)하는 기능을 제공한다.

// ✅ 예제: ObjectOutputStream을 사용한 객체 저장
import java.io.*;

class Student implements Serializable { // 직렬화를 위해 Serializable 인터페이스 구현
    private static final long serialVersionUID = 1L; // 직렬화 버전 관리
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class ObjectStreamExample {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.obj"))) {
            Student student = new Student("홍길동", 21);
            oos.writeObject(student); // 객체 저장
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// ✅ 예제: ObjectInputStream을 사용한 객체 읽기
import java.io.*;

public class ObjectReadExample {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.obj"))) {
            Student student = (Student) ois.readObject();
            System.out.println(student.name + " - " + student.age);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

💡 객체를 파일에 저장했다가 그대로 복원할 수 있다!