Developer.

[멋사 백엔드 19기] TIL 15일차 Java IO 심화

📂 목차


📚 본문

Java IO 심화

해당 패키지는 기본적으로 실습을 많이 하여야 익숙해지는 듯하니, 다양한 입출력들을 연습해보길 바란다.

우선 다음 흐름을 가져가자.

InputStream -> Reader -> System -> Writer -> OutputStream

InputStream(Byte Stream)

이전 포스트에서 스트림 자체는 byte 씩 읽어오는 것이라고 했다.

입력 스트림은 Java 애플리케이션에서 소스로부터 데이터를 읽는 데 사용되고 데이터는 파일, 배열, 주변 장치 또는 소켓 등 무엇이든 될 수 있다.
Java 에서 java.io.InputStream 클래스 는 모든 Java IO 입력 스트림의 기본 클래스이며 하위 클래스는 다음과 같이 있다.

  • AudioInputStream
  • ByteArrayInputStream
  • FileInputStream
  • FilterInputStream
  • InputStream
  • ObjectInputStream
  • PipedInputStream
  • SequenceInputStream
  • StringBufferInputStream

다 쓰진 않는다. 자주 쓰일거 같은 것들을 선택하여 쓰면 된다.

FileInputStream

당연하게도 File 을 입력 시킬 수 있다. 파일은 경로를 통해 설정할 수 있고, 설정 시에 소스가 정해진다.
다음 생성자들을 통해 소스를 설정할 수 있다.

생성자

  • FileInputStream(File file)
  • FileInputStream(String name)
  • FileInputStream(FileDescriptor fdObj)

소스가 정해졌으면 stream 으로 데이터가 흐르도록 초기화가 끝난 것이고,

주요 메서드

  • read(): 1 byte 읽기
  • read(byte[] b): b에 읽은 값들 저장
  • read(byte[] b, int off, int len): off 부터 len 길이 만큼의 byte 를 저장

등을 통해 읽어들일 수 있다. 해당 class 는 @Closable 하기에 try-with-resources 문을 통해 자동으로 close() 메서드를 실행시켜준다.

FilterInputStream

해당 클래스는 추상 클래스이고 protected 로 생성자가 보호되고 있다.

해당 클래스의 하위 클래스가 또 있는데, 다음 두 클래스가 자주 사용되는 듯하다.

BufferedInputStream

생성자로는 다음으로 초기화 가능하다.

  • BufferedInputStream(InputStream in)
  • BufferedInputStream(InputStream in, int size): size 는 버퍼사이즈이다.

기본적으로 상위클래스인 FilterInputStreamread() 세가지 메서드를 구현하기에 다 사용 가능하다.

DataInputStream

마찬가지로 DataInputStream(InputStream in) 을 통해 초기화를 하며,
특이하게도 여기에는 DataInput 이라는 인터페이스를 구현하고 있음을 볼 수 있다.

DataInput 인터페이스를 구현하면 이는 binary stream 에서 바이트 단위로 데이터를 읽어와서
그것을 자바의 기본 자료형으로 재구성 할 수 있게 해준다. 따라서 우리는 DataInputStream 으로
읽어오기만 하더라도 자동으로 자바의 primitives 로 변환시켜준다는 것이다.

예시를 보자.

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

public class DataInputExample {
    public static void main(String[] args) {
        try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
            int number = dis.readInt();        // 4바이트 정수 읽기
            double price = dis.readDouble();   // 8바이트 실수 읽기
            boolean flag = dis.readBoolean();  // 1바이트 불리언 읽기
            String text = dis.readUTF();       // Modified UTF-8 문자열 읽기

            System.out.println("number = " + number);
            System.out.println("price = " + price);
            System.out.println("flag = " + flag);
            System.out.println("text = " + text);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

위는 메서드들을 통해 쉽게 캐스팅을 하지 않고도 값을 읽어올 수 있음을 볼 수 있다.

  • readBoolean()
  • readByte()
  • readChar()
  • readDouble()
  • readFloat()
  • readFully(byte[] b)
  • readFully(byte[] b, int off, int len)
  • readInt()
  • readLine()
  • readLong()
  • readShort()
  • readUnsignedByte()
  • readUnsignedShort()
  • readUTF()
  • skipBytes()

기능들은 함수명 그대로이기에 설명은 생략한다.

ObjectInputStream

자바 객체를 읽어오는 스트림이며, 바이트 단위 스트림을 기반으로 파일, 네트워크 등에서 직렬화(serialized) 된 객체를 복원(deserialize) 하는 객체이다.

primitive 로 읽어오고 싶다면 FilterInputStreamDataInputStream 을, Object 로 읽어오고 싶다면 ObjectInputStream 을 쓰면 되겠다.

  • readObject(): 객체를 읽어들인다. 이때 반환은 Object 이므로 명시적 형변환이 필요하다.

여기서 모든 InputStream 은 안에 InputStream 을 넣어 초기화를 할 수 있는 생성자가 있다는 것을 저번에 posting 으로 썼었는데, 이를 통해 기능들을 늘릴 수 있다고 말했었다. 따라서 ObjectInputStream 은 기본 생성자가 protected 라서 생성자 안에 InputStream 을 무조건 넣어야 함을 볼 수 있는데, 이는 ObjectInputStream 혼자서 초기화가 되는게 아님을 볼 수 있다.

Reader(Char Stream)

Abstract class for reading character streams. The only methods that a subclass must implement are read(char[], int, int) and close(). Most subclasses, however, will override some of the methods defined here in order to provide higher efficiency, additional functionality, or both.

추상 클래스이며, 문자열 흐름을 읽어오기 위해 쓰는 메서드이다. 즉 Char Stream 이라고 보면 된다.

주요 메서드

  • read(char[] cbuf)
  • read(char[] cbuf, int off, int len)
  • read(CharBuffer target)

주요 하위 클래스

  • InputStreamReader
  • BufferedReader
  • FileReader

주요 예시들을 보자.

Character 를 맞는 decording method 설정해서 가져오기

public class InputStreamReaderExample {
    public static void main(String[] args) {
        try (
            FileInputStream fis = new FileInputStream("example.txt"); // 바이트 스트림
            InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); // 문자 변환
            BufferedReader br = new BufferedReader(isr) // 한 줄씩 읽기 편하게
        ) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

File을 읽기 위한 BufferedReader 의 readLine 기능을 활용

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (
            FileReader fr = new FileReader("example.txt");      // 문자 스트림
            BufferedReader br = new BufferedReader(fr)          // 버퍼링 + 편리한 readLine()
        ) {
            String line;
            while ((line = br.readLine()) != null) {           // 한 줄씩 읽기
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

OutputStream

주요 함수는 다음과 같다.

  • flush()
  • write(byte[] b)
  • write(byte[] b, int off, int len)

Flushable
Flushable 은 플러시될 수 있는 클래스의 대상이다. flush 메서드는 버퍼링된 출력을 기본 스트림에 쓰기 위해 호출된다.

Writer

Chaining Method

  • append(char c): 단일 문자 추가
  • append(CharSequence csq): 문자 시퀀스 전체 추가
  • append(CharSequence csq, int start, int end): 문자 시퀀스 일부 추가

반환값이 자기 자신이라 체이닝 가능

  • write(int c): 단일 문자 스트림에 쓰기
  • write(char[] cbuf): 문자 배열 전체 스트림에 쓰기
  • write(char[], int off, int len)
  • write(String str): 문자열 전체 스트림에 쓰기
  • write(String str, int off, int len)
PrintWriter

우리가 알고 있는 일반적인 print 기능이 들어가 있는 writer 이다. 필요하면 찾아서 쓰자.

BufferedWriter

단일 문자, 배열, 문자열 등을 효율적으로 읽고 쓸 수 있는 클래스이다.

자바에서는 Writer 클래스가 문자를 출력 스트림에 바로 보내는 역할을 하는데, write() 를 호출하면 곧바로 해당 문자들이 기본 스트림(File, OutputStream) 으로 전달되게 된다.

하지만 이렇게 바로 쓰면 특히 파일이나 네트워크 등에 비용이 큰 출력 작업에서는 굉장히 비효율적일 수 있다. 매번 writer() 가 호출될 때마다 문자를 byte 로 변환시키고 출력 스트림에 전송시켜야 하기에 이를 방지하고자 다음처럼 BufferedWriter 의 기능을 가지면서 일정량의 데이터가 모일 때 한 번에 출력하도록 하면 좋다.

import java.io.*;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (
            PrintWriter out = new PrintWriter(
                new BufferedWriter(
                    new FileWriter("foo.out")
                )
            )
        ) {
            out.println("Hello, BufferedWriter!");
            out.println("이제 출력이 훨씬 효율적입니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}