[우아한테크코스] 8월 25일 TIL

3 minute read

IOStream 학습테스트

입출력은 하나의 시스템에서 다른 시스템으로 데이터를 이동시킬 때 사용

입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동시킬 때 사용

자바는 스트림(Stream)으로부터 I/O를 사용하는데 InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.

FilterStream은 InputStream이나 OutputStream에 연결될 수 있다. FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)

Stream은 데이터를 바이트로 읽고 쓴다. 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다. Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다.


1. 출력

자바의 기본 출력 클래스는 java.io.OutputStream이다.

  • write 메서드

    public abstract void write(int b) throws IOException;

    OutputStream은 다른 매체에 바이트로 데이터를 쓸 때 사용한다.

    OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.

    예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때, DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.

    하지만 write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.

    write(byte[] data)write(byte b[], int off, int len) 메서드는 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.

  • BufferedOutputStream

    효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.

    BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.

    • Flush 메서드

      버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하도록 한다.

      flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.

      Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락(deadlock) 상태가 되기 때문에 flush로 해제하는 일이 필요하다.

  • ByteArrayOutputStream vs BufferedOutputStream

    ByteArrayOutputStream < OutputStream

    BufferedOutputStream < FilterOutputStream < OutputStream

  • close 메서드

    스트림 사용이 끝나면 항상 close() 메서드를 호출하여 스트림을 닫는다.

    장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.

    try-with-resources를 사용한다.

    try (outputStream){ }
    


2. 입력

자바의 기본 입력 클래스는 java.io.InputStream이다. InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.

  • read 메서드

    public abstract int read() throws IOException;

    InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.

    read() 메서드는 매체로부터 단일 바이트를 읽는데, 0부터 255 사이의 값을 int 타입으로 반환한다.

    int 값을 byte 타입으로 변환하면 -128부터 127 사이의 값으로 변환된다.

    그리고 Stream 끝에 도달하면 -1을 반환한다.

    • InputStream에서 바이트로 반환한 값을 문자열로 바꾸기

      new String(inputStream.readAllBytes())

  • BufferedInputStream

    필터는 필터 스트림, reader, writer로 나뉜다.

    필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.

    reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.

    BufferedInputStream은 데이터 처리 속도를 높이기 위해 데이터를 버퍼에 저장한다.

    InputStream 객체를 생성하고 필터 생성자에 전달하면 필터에 연결된다.

    버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 8192이다.

    BufferedInputStream < FilterInputStream < InputStream


3. 문자열 읽기

자바의 기본 문자열은 UTF-16 유니코드 인코딩을 사용한다.

바이트를 문자(char)로 처리하려면 인코딩을 신경 써야 한다.

InputStreamReader를 사용하면 지정된 인코딩에 따라 유니코드 문자로 변환할 수 있다.

reader, writer를 사용하면 입출력 스트림을 바이트가 아닌 문자 단위로 데이터를 처리하게 된다.

InputStreamReader를 사용해서 바이트를 문자(char)로 읽어온다.

필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.

ready 메서드를 이용해 이후에 남은 buffer가 있는지 확인할 수 있다.

InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

final StringBuilder actual = new StringBuilder();
while(bufferedReader.ready()) {
    actual.append(bufferedReader.readLine()).append("\r\n");
}

File 학습테스트

웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.

File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.

File 객체를 생성하려면 파일의 경로를 알아야 한다.

자바 애플리케이션은 resource 디렉터리에 정적 파일을 저장한다.

  • 파일 경로 알아내기

    final URL url = getClass().getClassLoader().getResource(fileName);
    final String actual = url.getFile();
    

    getClass().getResource()의 경우 호출한 패키지를 기준으로 상대적인 경로로 찾아오기 때문에 getClassLoader()가 필요하다.

    ClassLoader는 런타임에 동적으로 자바 클래스들을 로딩하는 일을 수행한다. 이 클래스 로더 덕분에 자바 런타임 시스템이 파일과 파일 시스템에 대해 알 필요가 없는 것이다.

  • 파일 내용 읽어오기

    Files 유틸클래스를 이용해 File과 관련된 다양한 일들을 수행할 수 있다. 이를 사용하려면 대체적으로 Path를 매개변수로 전달해주어야 한다.

    File의 toPath 메서드로 path를 얻을 수 있고 이때 path는 절대 경로이다. java의 nio 인터페이스가 io 인터페이스의 File에 대응되는 것으로 파일의 경로를 지정하기 위해 Path를 사용하므로 우리도 Path를 사용해야 한다.

    Path는 File 객체와 유사하게 사용될 수 있을 뿐만 아니라 toFile 메서드는 경로로부터 파일을 만들어낼 수도 있다.

    • 직접 InputStream, BufferedReader 만들어 읽어오는 버전
    final String fileName = "nextstep.txt";
      
    final URL url = getClass().getClassLoader().getResource(fileName);
    final File file = new File(url.getPath());
      
    final FileInputStream inputStream = new FileInputStream(file);
    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    final List<String> actual = bufferedReader.lines().collect(Collectors.toList());
    
    • Files라는 내부 유틸클래스에서 readAllLines하는 과정에서 InputStream, BufferedReader로 읽어오는 버전
    final String fileName = "nextstep.txt";
      
    final URL url = getClass().getClassLoader().getResource(fileName);
    final Path path = new File(url.getPath()).toPath();
      
    final List<String> actual = Files.readAllLines(path);
    

path vs file