JAVA - 파일 입출력 스트림(File I/O Stream)

이재원·2일 전
0

JAVA

목록 보기
11/11

스트림 입출력이란?

스트림(Stream)이란 흐르는 시냇물을 뜻하며, 컴퓨터 공학에서 스트림은 연속적인 데이터의 흐름 혹은 데이터 흐름을 형성해 주는 통로를 가라킨다.

즉, 각종 I/O 장치와의 데이터 이동에 사용되는 객체를 의미한다.

  • 사용자 콘솔 입출력
  • 파일 입출력
  • 네트워크 통신 등

스트림의 종류

  • 출력 스트림(Output Stream): 데이터를 쓰는 대상이 되는 스트림
  • 입력 스트림(Input Stream): 데이터를 읽어들이는 대상이 되는 스트림

스트림의 특징

FIFO 구조

  • 선입선출로 순서가 바뀌지 않는다.

단방향 구조

  • 자바에서는 읽기/쓰기가 동시에 지원되지 않으므로, 둘 다 필요한 경우 각각 열어줘야 한다.

지연가능성

  • 스트림에 넣어진 데이터가 처리되기 전까지 해당 스레드는 지연 상태에 놓인다.
  • 즉, 중간 연산(fileter, map 등)은 즉시 실행되지 않고, 최종 연산(collect, forEach 등)이 호출될 때만 실행된다.

연결 가능성

  • 여러 스트림 연산을 체인 형태로 연결하여 확장할 수 있다.
  • 아래는 바이트 스트림과 문자 스트림을 연결한 코드이다. 이 코드는 System.in으로 사용자의 키 입력을 받아 바이트 스트림으로 내보내며, rd는 바이트 스트림으로 구성하여 응용프로그램에게 전달한다.
    InputStreamReader rd = new InputStreamReader(System.in);
    // 입력 스트림으로부터 키 입력, c는 입력된 키의 문자 값
    int c = rd.read();

파일(File) 클래스

파일 및 디렉토리를 나타내고, 파일/디렉토리와 상호작용할 수 있는 다양한 메서드를 제공하는 클래스로 java.io 패키지에 포함되어 있다.

주요 기능으로는 파일/디렉토리의 생성, 삭제, 이름 변경, 조회, 탐색이 있다.

단, 파일에 직접적으로는 정보를 쓰거나 읽어올 수 없기 때문에 파일 입출력 스트림을 이용해야 한다.

바이트 스트림

데이터를 바이트 단위로 처리하는 스트림이다. 이는 파일, 네트워크 소켓, 메모리 등 다양한 입출력 소스와 데이터를 주고받을 때 사용된다. 바이트 스트림은 문자가 아닌 바이너리 데이터를 다룰 때 주로 사용된다.

바이트 스트림의 특징

  • 데이터를 8비트 단위(1바이트)로 처리한다.
  • 텍스트가 아닌 이미지, 비디오, 오디오, 바이너리 파일 등을 읽거나 쓸 때 적합하다.
  • 입출력을 처리하는 주요 클래스들은 InputStream과 OutputStream을 상속받는다.

바이트 스트림 클래스 계층 구조

InputStream과 OutputStream

InputStream

바이트 입력을 수행하는 데 필요한 메서드를 정의하는 추상 클래스이다.

객체를 생성하고, 생성된 객체와 바이트 스트림과 연결함으로써 파일을 open 할 수 있다. 또한 다른 장치들과도 바이트 스트림을 연결할 수 있다.

평소에 데이터를 입력할 때 사용했던 System.in 객체 역시 InputStream의 객체 중 하나이다. 사용법은 다음과 같다.

FileInputStream fin = new FileInputStream("{파일 경로}");

OutputStream

바이트 출력을 수행하는데 필요한 메서드를 정의하는 추상 클래스이다.

객체를 생성하고, 생성된 객체와 바이트 스트림과 연결함으로써 파일을 open한다. 또한 다른 장치들과도 바이트 스트림을 연결할 수 있다.

우리가 출력할 때 주로 사용했던 System.out 객체와 System.err 객체가 OuputStream의 객체 중 하나이다.

OutputStream outputStream = new FileOutputStream("output.txt");

FileInputStreamr과 FileOutputStream

FileInputStream

  • 바이트 단위로 파일을 읽는 기능을 제공한다.
  • 생성자의 매개 변수로 파일의 경로는 File 객체를 넘겨준다.
FileInputStream fin = new FileInputStream("input.txt");

FileOutputStream

  • 바이트 단위로 파일에 쓰는 기능을 제공한다.
  • 생성자의 매개 변수로 파일의 경로나 File 객체를 넘겨준다.
  • 객체 생성시 파일이 존재하면 그 파일에 쓰고, 존재하지 않으면 새로 생성한다.
    (Overwrite / Append)
FileOutputStream fout = new FileOutputStream("output.txt");

FileNotFoundException

  • 경로가 다르거나 파일이 존재하지 않을 때 발생하는 예외이다.
  • 따라서 경로가 존재하지 않을 때를 대비하기 위해 이 예외를 반드시 예외 처리 해줘야 한다.

버퍼 입출력과 파일 입출력

버퍼 입출력의 필요성

자주 운영체제 API가 호출될수록 하드 디스크 장치나 네트워크 장치가 자주 작동하게 되어 시스템의 효율은 나빠지고 프로그램 역시 여러 번 입출력을 진행해야 하므로 입출력의 실행 속도가 떨어진다. 이 경우 버퍼(Buffer)를 가지게 되면 보다 효율적으로 작동할 수 있다.

버퍼의 역할

버퍼란 데이터를 일시적으로 저장하기 위한 메모리이다. 파일 출력 스트림이 파일에 쓸 데이터를 버퍼에 모아 두었다가, 한 번에 운영체제 API를 호출하여 파일에 쓰게 하면, 운영체제의 부담을 줄이고 장치를 구동하는 일이 줄어들게 되어 시스템의 속도나 효율이 올라가게 될 것이다.

스트림의 연결(Channing)

버퍼 스트림을 사용하기 위해서는 입출력 스트림에 연결해서 사용해야 한다.

주요 클래스

BufferedInputStream

  • 하나의 물리적 입력 연산의 결과를 버퍼에 저장
  • 읽기 명령 요청시 이 버퍼로부터 결과들을 가져감
  • 연속된 read() 메서드 호출의 경우, 한번의 물리적 조작으로 데이터를 읽어서 버퍼를 꽉 채우고, 단지 메모리 버퍼로부터 데이터를 읽어 내는 것일 뿐이므로 개별적인 조작 때보다 훨씬 효율적이다.

BufferedOuputStream

  • 버퍼를 채워 출력장지로의 실제 전송이 한번에 대량으로 수행
  • flush() 메서드
    : 버퍼가 다 차지 않더라도 버퍼를 비워주는 기능으로, OuputStream에서 출력 속도의 향상을 위해서 정의 되었으나 실제로는 구현되지 않았음. 이 기능을 BufferedOuputStream 클래스에 구현.

버퍼 스트림 예제

import java.io.*;

public class ByteFileCopy {
  String srcFileName;
  String destFileName;

  ByteFileCopy(String srcFileName, String destFileName) {
    this.srcFileName = srcFileName;
    this.destFileName = destFileName;
  }

  void copy() {
    FileInputStream fis = null;
    BufferedInputStream bis = null;
    FileOutputStream fos = null;
    BufferedOutputStream bos = null;

    try {
      fis = new FileInputStream(srcFileName);
      bis = new BufferedInputStream(fis);
      fos = new FileOutputStream(destFileName);
      bos = new BufferedOutputStream(fos);

      int bData;
      while (true) {
        bData = bis.read();
        if (bData == -1)
          break;

        bos.write(bData);
      }
      System.out.println("Copy Success");
    } catch (FileNotFoundException fnfe) {
      System.err.print(fnfe.getMessage());
      System.err.print("복사를 취소합니다!");
    } catch (IOException ie) {
      ie.printStackTrace();
    } finally {
      try {
    	if (bis != null) bis.close();
        if (fis != null) fis.close();
        if (bos != null) bos.close();
        if (fos != null) fos.close();
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    ByteFileCopy bcopy = new ByteFileCopy("input.png", "output.png");
    bcopy.copy();
  }
}

DataStream

DataStream은 기본형 데이터를 읽고 쓰는 데 사용된다.

DataInputStream

  • 입력 스트림으로부터 기본형 데이터를 읽어들일 때 사용한다.
  • 생성자의 매개변수로 FileInputStream 객체를 넘겨 받는다.
  • 생성자는 어떠한 예외처리도 되어 있지 않다.

DataOuputStream

  • 출력 스트림에 기본형 데이터를 쓸 때 사용한다.
  • 생성자의 매개변수로 FileOuputStream 객체를 넘겨받는다.
  • 생성자는 어떠한 예외처리도 되어있지 않다.

DataStream 예제

import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) {
        String filePath = "data.bin";

        // 데이터를 파일에 쓰기
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filePath))) {
            dos.writeInt(42);        // 정수 쓰기
            dos.writeDouble(3.14159); // 실수 쓰기
            dos.writeBoolean(true); // 논리값 쓰기
            dos.writeUTF("Hello, DataStream!"); // 문자열 쓰기
            System.out.println("데이터 쓰기가 완료되었습니다.");
        } catch (IOException e) {
            System.err.println("데이터 쓰기 중 오류 발생: " + e.getMessage());
        }

        // 데이터를 파일에서 읽기
        try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
            int intValue = dis.readInt();           // 정수 읽기
            double doubleValue = dis.readDouble();  // 실수 읽기
            boolean booleanValue = dis.readBoolean(); // 논리값 읽기
            String stringValue = dis.readUTF();     // 문자열 읽기

            System.out.println("읽은 데이터:");
            System.out.println("정수: " + intValue);
            System.out.println("실수: " + doubleValue);
            System.out.println("논리값: " + booleanValue);
            System.out.println("문자열: " + stringValue);
        } catch (IOException e) {
            System.err.println("데이터 읽기 중 오류 발생: " + e.getMessage());
        }
    }
}
/*
읽은 데이터:
정수: 42
실수: 3.14159
논리값: true
문자열: Hello, DataStream!
*/

오브젝트 스트림과 직렬화

오브젝트 스트림(Object Stream)

오브젝트 스트림은 객체 데이터를 읽고 쓰는 데 사용되는 스트림이다. 이 스트림을 통해 객체를 직렬화(Serialization)하여 파일에 저장하거나 네트워크를 통해 전송할 수 있다.

ObjectOutputStream

: 객체 데이터를 파일 또는 출력 스트림에 기록한다.

ObjectInputStream

: 파일 또는 입력 스트림에서 객체 데이터를 읽어온다.

직렬화(Serialization)

직렬화는 객체의 상태를 연속된 바이트 형태로 변환하여 파일 저장, 네트워크 전송 등에 활용하는 기술이다. 반대로, 역직렬화(Deserialization)은 저장된 바이트 데이터를 다시 객체로 복원하는 과정이다.

직렬화 조건

  1. 직렬화하려는 클래스는 Serializable 인터페이스를 구현해야 한다. 다만, 추가로 재정의해야 할 메서드가 존재하지는 않는다.(이 클래스를 직렬화해도 괜찮다는 것을 표시)
  2. transient 키워드를 사용하여 직렬화하지 않을 필드를 명시할 수 있다.

오브젝트 스트림과 직렬화 예제

import java.io.*;

// 직렬화를 지원하는 클래스
class Person implements Serializable {
    private static final long serialVersionUID = 1L; // 클래스 버전 관리용 ID
    private String name;
    private int age;

    // transient 필드는 직렬화에서 제외됨(민감한 데이터이기 때문)
    private transient String password;

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

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";
    }
}

public class ObjectStreamExample {
    public static void main(String[] args) {
        String filePath = "person.ser";

        // 객체 생성
        Person person = new Person("Alice", 30, "securePassword");

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;

        // 객체 직렬화 (쓰기)
        try {
            oos = new ObjectOutputStream(new FileOutputStream(filePath));
            oos.writeObject(person);
            System.out.println("객체 직렬화 완료: " + person);
        } catch (IOException e) {
            System.err.println("직렬화 중 오류 발생: " + e.getMessage());
        } finally {
            // 스트림 닫기
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    System.err.println("ObjectOutputStream 닫기 중 오류 발생: " + e.getMessage());
                }
            }
        }

        // 객체 역직렬화 (읽기)
        try {
            ois = new ObjectInputStream(new FileInputStream(filePath));
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("객체 역직렬화 완료: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("역직렬화 중 오류 발생: " + e.getMessage());
        } finally {
            // 스트림 닫기
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    System.err.println("ObjectInputStream 닫기 중 오류 발생: " + e.getMessage());
                }
            }
        }
    }
}

// 객체 직렬화 완료: Person{name='Alice', age=30, password='securePassword'}
// 객체 역직렬화 완료: Person{name='Alice', age=30, password='null'} 

명품 JAVA programming - 황기태, 김효수
https://www.youtube.com/사람만이

profile
20학번 새내기^^(였음..)

0개의 댓글