백기선님의 스터디 13주차 과제

Java Stream

Stream(스트림)

데이터를 입출력하기 위한 추상화된 개념으로, 데이터가 이동하는 통로, 흐름을 나타낸다.

  • 스트림은 데이터를 운반하는데 사용되는 연결 통로이다.
  • 스트림은 FIFO 구조로 단방향 통신, 즉 하나의 스트림으로 입/출력을 동시에 처리할 수 없다.
  • 실제 데이터가 모두 전송되기 전까지 지연 상태를 유지한다.
  • 입/출력을 동시에 처리하려면 InputStream과 OutputStream 2개의 스트림이 필요하다.
  • 이 때, 먼저 보낸 데이터를 먼저 받게 되어있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.

InputStream, OutputStream

  • InputStream, OutputStream은 바이트 단위로 데이터를 전송하며 입/출력 대상에 따라 다르다.
    • FileInputStream, FileOutputStream : 파일
    • ByteArrayInputStream, ByteArrayOutputStream : 메모리(byte 배열)
    • PipedInputStream, PipedOutputStream : 프로세스(프로세스 간 통신)
    • AudioInputStream, AudioOutputStream : 오디오 장치
  • 제시된 입/출력 스트림은 모두 InputStream, OutputStream의 자손으로 읽고 쓰는데 필요한 추상 메서드를 구현해놓은 구현체이다.

  • InputStream
    • abstract int read() : 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 반환한다. 더 이상 읽을 바이트가 없으면 -1을 반환
    • int read(byte[] b) : 입력 스트림으로부터 b.length만큼의 바이트를 읽고 읽은 바이트를 b에 저장하고 읽은 바이트 수를 반환한다. 더 이상 읽을 바이트가 없으면 -1을 반환
    • int read(byte[] b, int off, int len) : 입력 스트림으로부터 len만큼의 바이트를 읽고 읽은 바이트를 b[off]부터 저장하고 읽은 바이트 수를 반환한다. 더 이상 읽을 바이트가 없으면 -1을 반환
  • OutputStream
    • abstract void write(int b) : 출력 스트림으로 1바이트를 쓴다.
    • void write(byte[] b) : 출력 스트림으로 b.length만큼의 바이트를 쓴다.
    • void write(byte[] b, int off, int len) : 출력 스트림으로 b[off]부터 len만큼의 바이트를 쓴다.

보조 스트림(Decorator Stream)

  • 일반적으로 입/출력 작업은 작은 단위의 데이터를 여러 번에 걸쳐 전송하는 경우가 많다.
  • 이 때, 매번 작업 수행 시 실제 입/출력 장치와 통신하는 비용이 발생하므로 작업이 느려질 가능성이 있다.
  • 이러한 비효율성을 개선하기 위해 스트림의 기능을 보완하기 위한 보조 스트림인 버퍼를 사용한다.
  • 버퍼는 입/출력 횟수(시스템 콜)를 줄여 성능 상 이점이 생기는 것이다.
// 스트림 생성
FileInputStream fileInputStream = new FileInputStream("test.txt");

// 보조 스트림을 생성
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

// 사이즈 정의 생성
BufferedInputStream bis = new BufferedInputStream(fileInputStream, 8192);

// 보조스트림을 이용해 데이터를 읽기
bufferedInputStream.read();

보조 스트림 종류

  • FilterInputStream, FilterOutputStream : 필터를 이용한 입출력 처리
  • BufferedInputStream, BufferedOutputStream : 버퍼를 이용한 입출력 처리
  • DataInputStream, DataOutputStream : 자바의 primitive type을 그대로 입출력 처리
  • SequenceInputStream : 두 개의 스트림을 하나로 연결하여 입출력 처리
  • LineNumberInputStream : 읽어온 데이터의 라인번호를 카운트
  • ObjectInputStream, ObjectOutputStream : 객체를 직렬화하여 입출력 처리
  • PrintStream : 출력 스트림에 출력하는 메서드를 추가
  • PushbackInputStream : 버퍼로 읽어온 데이터를 다시 되돌림

문자기반 스트림 Reader, Write

  • 바이트기반의 스트림의 단점을 보완하기 위한 문자기반의 스트림이다.
  • 문자 데이터 입출력시 사용한다.

종류

  • FileReader, FileWriter : 파일 입출력
  • ByteArrayInputStream, ByteArrayOutputStream : 메모리 입출력
  • PipedReader, PipedWriter : 프로세스 간 통신
  • StringBufferInputStream, StringBufferOutputStream : 문자열 입출력
  • CharArrayReader, CharArrayWriter : 문자 배열 입출력

NIO(New Input/Output)

  • JDK 1.4에 등장한 NIO는 버퍼, 채널 기반으로 동작하는 입/출력이다.
  • 스트림과 달리 양방향으로 입/출력이 가능하여 입/출력을 위한 별도의 채널을 만들 필요가 없다.
  • 일반 I/O는 출력 스트림이 1byte를 쓰면 입력 스트림이 1byte를 읽는다.
    • NIO는 기본적으로 버퍼를 사용해 입출력을 사용하여 더 높은 성능을 가진다.
  • blocking과 non-blocking 특징을 모두 갖는다.
    • 입출력 작업 준비가 완료된 채널만 선택하여 작업 스레드가 처리하기 때문에 작업 스레드가 blocking되지 않는다.

NIO Package

  • java.nio : 다양한 버퍼 클래스
  • java.nio.channels : 파일 채널, TCP, UDP 채널 클래스
  • java.nio.channels.spi : java.nio.channels 패키지를 위한 서비스 제공자 클래스
  • java.nio.charset : 문자셋 인코딩 및 디코딩 클래스
  • java.nio.charset.spi : java.nio.charset 패키지를 위한 서비스 제공자 클래스
  • java.nio.file : 파일 및 파일 시스템 접근 클래스
  • java.nio.file.attribute : 파일 및 파일 시스템 속성에 접근하기 위한 클래스

표준 스트림(System.in, System.out, System.err)

  • 콘솔을 통한 데이터 입력과 데이터 출력을 의미한다.
  • Java 애플리케이션 실행과 동시에 사용할 수 있게 자동적으로 생성되어 별도로 생성 코드작성 필요가 없다.

System 클래스


public final static InputStream in = null;

...

public final static PrintStream out = null;

...

public final static PrintStream err = null;
  • in, out, err는 System 클래스의 선언된 static 변수다.
    • System.in : 콘솔로부터 데이터를 입력받기
    • System.out : 콘솔로 데이터를 출력
    • System.err : 콘솔로 데이터를 출력
  • 이 때, 버퍼를 이용하는 BufferedInputStream, BufferedOutputStream의 인스턴스를 사용한다.

setOut, setErr, setIn

  • 최초에는 System.in, out, err의 입출력대상이 콘솔이지만 입/출력을 콘솔 이외의 다른 입/출력 대상으로 변경할 수 있다.
    • static void setOut(PrintStream out) : System.out의 출력을 지정된 PrintStream으로 변경
    • static void setErr(PrintStream err) : System.err의 출력을 지정된 PrintStream으로 변경
    • static void setIn(InputStream in) : System.in의 출력을 지정된 InputStream으로 변경
public class setEx {

    public static void main(String[] args) {

        try (FileOutputStream fos = new FileOutputStream("test.txt");
             PrintStream ps = new PrintStream(fos)) {

            // System.out 출력 대상을 test.txt 파일로 변경
            System.setOut(ps);

            System.out.println("out - ex1");
            System.err.println("err - ex1");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// test.txt에 out - ex1 반영

파일 읽고 쓰기

public class InputEx {
  public static void main(String[] args) {
      
    try {
      FileInputStream file = new FileInputStream("C:\\test.txt");
      FileOutputStream fileOut = new FileOutputStream("C:\\output.txt", true);


      BufferedInputStream buffIn = new BufferedInputStream(file);
      BufferedOutputStream buffOut = new BufferedOutputStream(fileOut);

      int ch;

      while((ch = buffIn.read()) != -1) {
        buffOut.write(ch);
      }


      buffIn.close();
      buffOut.close();
      file.close();
      fileOut.close();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
  • 입력파일 C:\text.txt에서 데이터를 버퍼를 사용해 c:\output.txt에 쓰는 예시이다.
  • FileInputStream과 FileOutputStream을 사용하여 입력 파일과 출력 파일을 open
  • BufferedInputStream과 BufferedOutputStream을 생성하여 각각 file과 fileOut 스트림을 감싼다.
  • while 루프문으로 입력 파일에서 데이터를 읽어와 읽은 데이터를 출력 파일에 write
  • 입력 파일의 끝에 도달하면 read()는 -1을 반환하므로, while 루프를 종료
  • buffIn, buffOut, file, fileOut 스트림을 각각 닫아주고, 입출력 작업을 완료

참고자료

  • https://docs.oracle.com/javase/tutorial/essential/io/streams.html
  • https://five-cosmos-fb9.notion.site/I-O-af9b3036338c43a8bf9fa6a521cda242