Java; I/O Stream

🗓️

자바 입출력과 스트림

스트림이란?

  • 자바에서 모든 입출력은 Stream을 통해 이루어진다. 파일 디스크 키보드 모니터 메모리 네트워크 등등 모두 포함.
  • Stream이란 용어는 네트워크에서 유래됐다.
  • 입출력장치가 다양하기 때문에 자바는 입출력 장치와 무관하고 일관성 있게 프로그램을 구현할 수 있도록 일종의 가상 통로인 Stream을 제송한다.
  • Source와 target에 따라 각각 다른 Stream 클래스를 제공한다.

입력 스트림과 출력 스트림

  • 입력 스트림 : 어떤 대상으로부터 자료를 읽어들일때 사용하는 스트림을 입력 스트림이라고 한다.
  • 출력 스트림 : 파일에 저장할 때는 출력 스트림을 사용한다.
  • 스트림은 단방향이다. 입력과 출력을 동시에 할 수 없다.
  • 스트림의 이름을 보면 입력인지 출력인지 알 수 있다.
  • 입력 스트림의 예
    • FileInputStream, FileReader, BufferedInputStream, BufferedReader
  • 출력 스트림의 예
    • FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter

바이트 단위 스트림과 문자 스트림

  • 원래 자바의 스트림 단위는 바이트다.
  • 그런데 char형은 2바이트라 1바이트만 읽으면 특수문자는 깨진다.
  • 그래서 문자를 위한 스트림은 별도로 있다.
  • 바이트 스트림의 예
    • FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
  • 문자 스트림의 예
    • FileReader, FileWriter, BufferedReader, BufferedWriter

기반 스트림과 보조 스트림

  • 기반 스트림 : 어떤 스트림이 자료를 직접 읽거나 쓰는 기능을 제공하는 스트림
  • 보조 스트림 : 직접 읽고 쓰는 기능은 없고 다른 스트림에 부가기능을 제공하는 스트림
  • 기반 스트림의 예
    • FileInputStream, FileOutputStream, FileReader, FileWriter
  • 보조 스트림의 예
    • InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream
  • 이름만으로 기억하기는 어렵고 많이 사용하는 스트림 위주로 기억하자.

표준 입출력

  • 화면에 출력하고 입력받는 표준 입출력 클래스
  • static이기 때문에 인스턴스를 생성할 필요가 없다.
  • System.out은 표준 출력을 위한 객체다.
  • System class의 변수
    • static PrintStream out : 표준 출력 스트림
    • static InputStream int : 표준 입력 스트림
    • static OutputStream err : 표준 오류 출력 스트림

PrintStreamOutputStream의 하위 클래스다.

System.in

Scanner

  • Scanner는 java.util 패키지에 있는 입력 클래스다.
  • Scanner 클래스는 문자 뿐만 아니라 정수 실수 등 다른 자료형도 읽을 수 있다.
  • Scanner class의 생성자
    • Scanner(File source) : 파일을 매개변수로 받아 Scanner를 생성한다.
    • Scanner(InputStream source) : 바이트 스트림을 매개변수로 받아 Scanner를 생성한다.
    • Scanner(String source) : String을 매개변수로 받아 Scanner를 생성한다.
  • Scanner scanner = new Scanner(System.in)은 문자열을 입력하기 위해 자주 쓰는 방법이다. – 코드 보기
  • Scanner class의 메소드
    • boolean nextBoolean(), byte nextByte(), short nextShort(), int nextInt(), long nextLong(), float nextFloaf(), double nextDouble(), String nextLine()

Console

바이트 단위 스트림

  • 입출력 기능을 구현하는데 기본으로 알아야 하는 클래스와 메소드. (바이트 스트림, 문자 스트림, 보조 스트림)

InputStream

  • 바이트 단위로 읽는 스트림 중 최상위 스트림.
  • 추상 클래스로 각 하위 스트림 클래스가 상속받아 사용한다.
  • 주로 사용되는 클래스들
    • FileInputStream : 파일에서 바이트 단위로 자료를 읽는다.
    • ByteArrayInputStream : Byte배열 메모리에서 바이트 단위로 자료를 읽는다.
    • FilterInputStream : 기반 스트림에서 자료를 읽을때 추가 기능을 제공하는 보조스트림.
  • InputStream의 메소드
    • int read() : 읽은 바이트의 수를 반환
    • int read(byte b[]) : 배열의 읽은 바이트 수를 반환
    • int read(byte b[], int off, int len) : offset부터 length만큼 읽고 바이트 수를 반환
    • void close() : 리소스 닫기

FileInputStream

  • 파일에서 바이트 단위로 자료를 읽어들일때 사용하는 스트림 클래스
  • FileInputStream의 생성자
    • FileInputStream(String name) : 파일 이름을 매개변수로 받아 입력 스트림을 생성.
    • FileInputStream(File f) : File 클래스를 메시지로 받아 입력 스트림을 생성.

파일에서 자료 읽기

파일 끝까지 읽기

public class FileInputStreamTest2 {

    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt")) {
            int i;
            while ((i = fis.read()) != -1) {
                System.out.println((char) i);
            }
            System.out.println("end");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • fis.read()에서 -1이 반환되면 더이상 읽을 바이트가 없다는 뜻이다.

int read(byte[] b)

ABCDEFGHIJKLMNOPQRSTUVWXYZ
public class FileInputStreamTest3 {

    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input2.txt")) {
            byte[] bs = new byte[10];
            int i;
            while ((i = fis.read(bs)) != -1) {
                for (byte b : bs) {
                    System.out.print((char) b);
                }
                System.out.println(": " + i + " 바이트 읽음.");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
  • 실행 결과
ABCDEFGHIJ: 10 바이트 읽음.
KLMNOPQRST: 10 바이트 읽음.
UVWXYZQRST: 6 바이트 읽음.
end
  • 마지막 6바이트에 QRST가 남아있는것은 for (byte b : bs)로 전체 순회를 했기 때문이다.
  • 그래서 bs를 순회하는 것이 아니라 읽는 바이트 수대로만 순회하면 된다.
for (int k = 0; k < i; k++) {
    System.out.print((char) bs[k]);
}

OutputStream

  • 바이트 단위로 쓰는 스트림 중 최상위 스트림이다.
  • OutputStream 하위 클래스
    • FileOutputStream : 바이트 단위로 파일에 자료를 쓴다.
    • ByteArrayOutputStream : Byte배열에 바이트 단위로 자료를 쓴다.
    • FilterOutputStream : 보조 스트림.
  • OutputStream 메소드
    • void writer(int b) : 한 바이트를 출력한다.
    • void write(byte[] b : 배열에 있는 자료를 출력한다.
    • void write(byte b[], int off, int len) : 배열에 있는 자료의 offset위치 부터 length만큼 자료를 출력한다.
    • void flush() : 출력을 위해 출력 버퍼를 비운다.
    • void close() : 리소스 닫기. 출력 버퍼가 비워진다.

FileOutputStream

  • 파일에 바이트 단위 자료를 출력하기 위해 사용하는 스트림이다.
  • FileOutputStream 생성자
    • FileOutputStream(String name) : name파일에 대한 출력 스트림 생성
    • FileOutputStream(String name, boolean append) : append값이 true면 파일 스트림을 닫고 다시 생성할때 파일의 끝에 이어서 작성한다.
    • FileOutputStream(File f,) : File클래스 출력스트림 생성
    • FileOutputStream(File f, boolean append)
  • 해당 경로에 파일이 없으면 파일을 새로 생성한다.
  • 처음부터 덮어쓸지 기존에서 이어쓸지 결정할 수 있다. (append)

write()

write(byte[] b)

public class FileOutputStreamTest3 {

    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("output.txt", true);
        try (fos) {
            byte[] bs = new byte[26];
            byte data = 65;
            for (int i = 0; i < bs.length; i++) {
                bs[i] = data;
                data++;
            }
            fos.write(bs);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
  • fos.write(bs) : 배열을 쓰기한다.

flush(), close()

  • 출력 스트림에서 flsuh()메소드의 기능은 강제로 자료를 출력하는 것이다. write()를 값을 썼다고 해도 바로 파일이나 네트워크로 전송되지 않고 출력을 위한 자료가 쌓이는 출력 버퍼에 어느정도 자료가 모여야 출력된다.
  • 따라서 자료의 양이 출력할 만큼 많지 않으면 write()로 출력 했어도 파일에 쓰이지 않거나 전송되지 않을 수 있다.
  • 이런 경우 flush()를 호출해야 한다.
  • close()는 안에서 flush()를 호출해 버퍼가 비워지면서 남은 자료가 모두 출력된다.

문자 단위 스트림

Reader

  • 문자 단위로 읽는 스트림 중 퇴상위 스트림으로 다음 하위 클래스를 주로 사용한다
  • FileReader : 파일에서 문자 단위로 읽는 스트림 클래스
  • InputStreamReader : 바이트 단위로 읽은 자료를 문자로 변환해주는 보조 스트림 클래스
  • BufferedReader : 문자로 읽을 때 배열을 제공하여 한꺼번에 읽을 수 있는 기능을 제공해 주는 보조 스트림
  • 아래와 같은 메소드를 제공한다
  • int read()
  • int read(char[] buf)
  • int read(char[], int off, int len)
  • void close()

FileReader

  • 생성자
    • FileReader(String name) : 파일 이름을 매개변수로 받아 입력 스트림을 생성한다.
    • FileReader(File f) : File 클래스 정보를 매개변수로 받아 입력 스트림을 생성한다
  • 한글 같은 2바이트 단위 문자들은 1바이트 단위로 읽어오면 깨진다. 따라서 문자를 입출력할때 문자 스트림을 사용해야 한다.
  • 예제코드
public class FileReaderTest {

    public static void main(String[] args) throws FileNotFoundException {
        FileReader fr = new FileReader("reader.txt"); // 미리 만들어야한다.
        try (fr) {
            int i;
            while ((i = fr.read()) != -1) {
                System.out.print((char) i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Writer

  • 문자 단위로 출력하는 스트림 중 최상위 스트림으로 다음 하위 클래스가 있다
  • FileWriter : 파일에 문자 단위로 출력하는 스트림 클래스다
  • OutputStreamWriter : 파일에 바이트 단위로 출력한 자료를 문자로 변환해주는 보조 스트림이다
  • BufferedWriter : 문자로 쓸 때 배열을 제공하여 한꺼번에 쓸 수 있는 기능을 제공하는 보조 스트림이다.
  • 다음과 같은 메소드를 제공한다
    • void write(int c)
    • void write(char[] buf)
    • void write(char[] buf, int off, int len)
    • void write(String str)
    • void write(String str, int off, int len)
    • void flush()
    • void close()

FileWriter

  • 다른 스트림 클래스와 마찬가지로 생성자를 사용해서 스트림을 생성한다.
  • FileOutputStream 과 마찬가지로 출력파일이 없으면 생성한다.
  • 생성자
    • FileWriter(String name)
    • FileWriter(String name, boolean append)
    • FileWriter(File f, )
    • FileWriter(File f, boolean append)
  • 예제 코드
public class FileWriterTest {

    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("writer.txt");
        try (fw) {
            fw.write('강');
            char buf[] = {'H', 'E', 'L', 'L', 'O'};

            fw.write(buf);
            fw.write("안녕하세요");
            fw.write("65");

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

}

보조 스트림

  • 스트림에 보조기능을 추가하는 보조 스트림.
  • Wrapper 스트림이라고도 함.
  • 스스로는 입출력 기능이 없다. 생성자의 매개변수로 다른 스트림을 받으면 자신이 감싸고 있는 스트림이 읽거나 기능을 수행할 때 기능을 추가한다.
  • 데코레이터 패턴의 유명한 예.

FilterInputStream, FilterOutputStream

  • 보조스트림의 상위 클래스다. 모든 보조 스트림은 FilterInputStream이나 FilterOutputStream을 상속 받는다.
  • 이들 클래스를 상속받은 보조 클래스도 상위 클래스에 디폴트 생성자가 없어므로 다른 스트림을 매개변수로 받아 상위 클래스를 호출해야 한다.
  • 다른 보조 스트림을 매개변수로 전달 받을 수도 있다.

InputStreamReader OutputStreamWriter

  • 문자를 위해서 Reader나 Writer에서 상속 받은 스트림을 사용해 자료를 읽거나 써야 한다. 하지만 바이트 자료만 입력되는 스트림도 있다. (System.in)
  • 또한 네트워크에서 소켓이나 인터넷이 연결되었을 때 읽거나 쓰는 스트림은 바이트 단위인 InputStream과 OutputStream이다.
  • 이런 바이트 스트림을 문자로 변환해 주는 보조 스트림이 이 두 스트림이다.
  • InputStreamReader 생성자
    • InputStreamReader(InputStream in)
    • InputStreamReader(InputStream in, Charset cs)
    • InputStreamReader(InputStream in, CharsetDecorder dec)
    • InputStreamReader(InputStream in, String charsetName) : UTF-8, UTF-16
  • charset을 지정하지 않으면 시스템 기본 charset이 지정된다.
public class InputStreamReaderTest {

    public static void main(String[] args) throws FileNotFoundException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("reader.txt"));
        try (isr) {
            int i;
            while ((i = isr.read()) != -1) {
                System.out.print((char) i);
            }

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

}
  • 이러면 바이트 단위 스트림에서 문자를 읽어올 수 있다.
  • 표준 입출력 스트림 System.in과 .out은 모두 바이트 스트림이다. 특히 System.in은 콘솔 화면에서 한글을 읽을려면 InputStreamReader를 사용해야 한다.
  • Scanner클래스는 이런 변환이 필요없다.

Buffered 스트림

  • 이미 생성된 스트림에 배열 기능을 추가해 더 빠르게 입출력을 실행할 수 있는 버퍼링 기능을 제공한다.
  • BufferedInputStream : 바이트 단위로 읽는 스트림에 버퍼링 기능을 제공한다.
  • BufferesOutputStream : 바이트 단위로 출력하는 스트림에 버퍼링 기능을 제공한다.
  • BufferedReader : 문자 단위로 읽는 스트림에 버퍼링 기능을 제공한다.
  • BufferedWriter : 문자 단위로 출력하는 스트림에 버퍼링 기능을 제공한다.
  • Buffered 스트림은 맴버 변수로 8192바이트 배열을 가지고 있다.
  • 한번 자료를 읽을때 8KB를 한번에 읽고 쓸 수 있어 1바이트씩 작업할때 보다 훨씬 빠르다.
  • 배열의 크기 역시 지정할 수 있다.
  • 비교 예제 코드
public class FileCopyTest {

    public static void main(String[] args) throws FileNotFoundException {
        long mills = 0;
        FileInputStream fis = new FileInputStream("origin.zip");
        FileOutputStream fos = new FileOutputStream("copy.zip");
        try (fis; fos) {
            mills = System.currentTimeMillis();
            int i;
            while ((i = fis.read()) != -1) {
                fos.write(i);
            }
            mills = System.currentTimeMillis() - mills;
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(mills + "");
    }
}
  • 약 8MB 파일을 복사하는데 20151ms가 소요됐다 (느리다)
  • 아래는 BufferedInputStream을 활용한 개선 코드
public class BufferedStreamTest {

    public static void main(String[] args) throws FileNotFoundException {
        long mills = 0;
        BufferedInputStream bis =
            new BufferedInputStream(
                new FileInputStream("origin.zip"));
        BufferedOutputStream bos =
            new BufferedOutputStream(
                new FileOutputStream("copy.zip"));
        try (bis; bos) {
            mills = System.currentTimeMillis();
            int i;
            while ((i = bis.read()) != -1) {
                bos.write(i);
            }
            mills = System.currentTimeMillis() - mills;
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(mills + "");
    }
}
  • 실행결과 200ms정도로 엄청나게 빠른것을 알 수 있다.

DataInputStream, DataOutputStream

  • 바이트나 문자가 아닌 메모리 저장된 상태를 그대로 읽거나 쓰는 보조 스트림이다.
  • DataInputStream(InputStream in)
  • DataOutputStream(OutputStream out)
  • 파일에 여러 자료형 값을 저장하고 불러오는 예제
public class DataStreamTest {

    public static void main(String[] args) throws FileNotFoundException {
        FileOutputStream fos = new FileOutputStream("data.txt");
        DataOutputStream dos = new DataOutputStream(fos);
        try (fos; dos) {
            dos.writeByte(100);
            dos.writeChar('A');
            dos.writeInt(10);
            dos.writeFloat(3.14f);
            dos.writeUTF("뷁");
        } catch (IOException e) {
            e.printStackTrace();
        }

        FileInputStream fis = new FileInputStream("data.txt");
        DataInputStream dis = new DataInputStream(fis);
        try (fis; dis) {
            System.out.println(dis.readByte());
            System.out.println(dis.readChar());
            System.out.println(dis.readInt());
            System.out.println(dis.readFloat());
            System.out.println(dis.readUTF());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Decorator pattern

  • 자바IO스트림은 데코레이터 패턴이다.
  • 데코레이터 패턴에서는 클래스는 실제로 입출력이 가능한 클래스와 그렇지 않은 클래스로 구분된다.
  • 기능이 동적으로 추가되는 클래스를 데코레이터라고 한다.
  • 보조스트림은 자료형만 맞는다면 다른 보조스트림에 기능을 더해줄 수 있다
WhippedCreamCoffee whippedCreamCoffee = new WhippedCreamCoffee(new MocaCoffee(new LatteCoffee(new KenyaCoffee())));

🏷️