[새배내] Level4를 진행하며 새로 배운 내용

Junseo Kim·2021년 8월 25일
1

[우아한테크코스3기]

목록 보기
26/27
post-thumbnail

자바 입출력

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

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

스트림은 데이터를 바이트로 읽고 쓴다. 따라서 바이트가 아닌 문자를 읽고 쓰기 위해서는 Reader와 Writer라는 클래스를 사용해야한다.

OutputStream

  • 자바의 기본 출력 클래스
  • 다른 매체에 바이트로 데이터를 쓸 때 사용
// OutputStream.java

public abstract class OutputStream implements Closeable, Flushable {

    // 데이터를 바이트로 출력하기 때문에 비효율적
    public abstract void write(int b) throws IOException;

    // 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }
    
    // 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적
    public void write(byte b[], int off, int len) throws IOException {
        Objects.checkFromIndexSize(off, len, b.length);
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }
}

BufferedOutputStream과 ByteArrayOutputStream 차이

ByteArrayOutputStream: 메모리의 바이트 배열에 데이터를 쓴다.
BufferedOutputStream: 입출력 작업을 효율적으로 만들기 위해 다른 기본 스트림을 래핑하고 기본 스트림에 대한 버퍼링을 제공한다.

BufferedOutputStream은 ByteArrayOutputStream의 기능을 보완해준다. 자체적인 입출력 기능은 없으며, 입출력 기능은 기반 스트림에 위임한다. 따라서 생성시 OutputStream을 인자로 받는다.

버퍼링을 사용하려는 이유
큰 데이터 블록을 파일 시스템에 쓰는 것이 바이트 단위로 쓰는 것보다 더 효율적이기 때문. 버퍼에 데이터 조각들을 수집한 다음 한 번에 디스크에 쓰는 것이 효율적

효율적인 전송을 위해 BufferedOutputStream 필터를 연결하여 스트림에서 버퍼링 사용 가능

버퍼링을 사용하면 OutputStream을 사용할 때 flush 메서드 사용(버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송. 스트림은 동기로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락 상태가 되기 때문에 flush로 해제)

InputStream

  • 자바의 기본 입력 클래스
  • 다른 매체로부터 바이트로 데이터를 읽을 때 사용

inputStream에서 바이트로 반환한 값을 문자열로 바꾸는 방법

String은 byte[]를 인자로 받아 문자열을 생성해줄 수 있다. InputStream의 readAllBytes 메서드(intput stream의 모든 바이트들을 읽는 메서드)의 반환값이 byte[]이므로 new String(inputStream.readAllBytes()); 이런식으로 바이트로 반환한 값을 문자열로 바꿀 수 있다.

readAllBytes가 아닌 일반 read 메서드는 단일 바이트를 읽어서 0 ~ 255 사이의 int값을 반환한다.(int 값을 byte로 변화하면 -128 ~ 127의 값으로 변환되며 스트림의 끝에 도달 시 -1을 반환한다)

close

스트림 사용 후엔 close 메서드 사용하여 스트림을 닫아주는 것이 좋다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스 누수가 발생한다. try-with-resource 구문을 사용해서 자동으로 자원을 반납하도록 해도 된다.

필터

  • 필터 스트림, reader, writer로 나뉨
  • 바이트를 다른 데이터 형식으로 변환할 때 사용
  • reader, writer는 바이트가 아닌 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 문자를 처리하는 데 사용
  • 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 8192

BufferedReader 전체 조회

@Test
void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
    // given
    final String emoji = String.join("\r\n",
            "😀😃😄😁😆😅😂🤣🥲☺️😊",
            "😇🙂🙃😉😌😍🥰😘😗😙😚",
            "😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
            "");
    final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

    // when
    final StringBuilder actual = new StringBuilder();
    String line = "";
    while ((line = bufferedReader.readLine()) != null) {
        actual.append(line).append("\r\n");
    }

    // then
    assertThat(actual).hasToString(emoji);
}

BufferedReader의 ready 메서드 이용 방법

@Test
void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
    // given
    final String emoji = String.join("\r\n",
            "😀😃😄😁😆😅😂🤣🥲☺️😊",
            "😇🙂🙃😉😌😍🥰😘😗😙😚",
            "😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
            "");
    final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

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

    // then
    assertThat(actual).hasToString(emoji);
}

파일 처리

웹 서버는 사용자의 요청에 따라 html 파일을 제공해줘야한다. File 클래스를 사용하여 파일을 읽어서 사용자에게 전달해준다.

resource 디렉터리 내부 파일의 경로찾기

클래스를 로딩하기 위해서는 class 파일을 바이트로 읽어 메모리에 로딩해야한다. 클래스 패스에 존재하는 모든 클래스 파일들(그림 파일, 사운드 파일, 설정 파일 등등 모든 파일들)은 클래스로더를 통해 찾을 수 있다.

클래스로더: 바이트 코드를 읽어들여 class 객체를 생성하는 역할. 클래스를 로딩하는 시점은 런타임시.(참고: [Java] JVM, JRE, JDK - 클래스 로더 시스템 부분)

그럼 클래스로더는 어떻게 가져올까? 각 스레드마다 다른 클래스 로더를 사용할 수 있기 때문에 현재 스레드를 찾아서 클래스로더를 가져와야한다.

final URL url = Thread.currentThread().getContextClassLoader().getResource(fileName);
final String path = url.getPath();

참고

관련 클래스

URL

웹에서 사용하는 URL에 관한 정보를 추상화 한 클래스

URI

URL을 일반화 시킨 클래스. 순수하게 리소스를 식별하고 URI를 분석하는 기능만 제공. URI가 참조하는 리소스를 가져오는 메서드는 제공하지 않음.

URL URI 클래스 차이
https://codedragon.tistory.com/5499

Path

File 클래스의 업그레이드 버전. 경로를 조작하는데 사용.

Paths

Path 클래스를 생성하는데 사용하는 헬퍼클래스

File

파일이나 폴더에 대한 제어를 할 때 사용.

Files

파일을 손쉽게 다룰 수 있는 유틸성 메서드를 모아둔 클래스.

스레드 관련

ExecutorService

병렬 작업시 여러 작업을 효율적으로 처리하기 위해 제공되는 자바 라이브러리. 작업을 분리하고 수행하는 작업을 직접 구현하려면, 각각 스레드를 생성하여 작업을 처리하고, 처리 완료시 해당 스레드를 제거하는 작업을 해줘야한다. 이런 작업을 쉽게 처리하게 해주는 클래스이다. ThreadPool을 구현하기가 매우 쉽다.

참고

CountDownLatch

자바에서 제공하는 Concurrency(병행성)에 관련된 API. 스레드를 N개 실행했을 때, 일정 개수의 스레드가 모두 끝날때까지 기다려야 다음 작업을 진행할 수 있거나 다른 스레드를 실행시킬 수 있는 경우에 사용한다.

CountDownLatch 초기화 시 int값 count를 넣어준다. 스레드는 작업 마지막에 CountDownLatch의 countDown() 메서드를 호출한다. 그러면 초기화 시 넣어줬던 count값이 감소한다. 즉, 각 스레드는 작업 완료를 countDown() 메서드로 알려준다. 스레드의 작업이 끝나길 기다리는 쪽에서는 CountDownLatch의 await() 메서드를 호출한다. 그러면 현재 메서드가 실행중인 메인스레드는 진행을 멈추고, CountDownLatch의 count가 0이 될 때까지 기다린다.

참고


스프링 MVC request -> response 과정

dispatcherServlet → getHandler → getHandlerAdapter → HandlerInterceptor → handlerAdapter.handle → argumentResolver → Handler(Controller)

스프링 세션

실제로는 tomcat에서 생성된다.

org.apache.catalina.session 패키지의 ManagerBase 클래스의 createSession 메서드에서 JSESSIONID를 생성(createSession 메서드는 실제로 getSession과 같은 요청이 있을때만 일어난다) 즉 요청이 들어온다고 JSESSIONID를 생성하는 것은 아니고 session을 호출해야지 생성된다.

실제로 JSESSIONID(스프링 세션 사용시는 SESSION)쿠키에 쓰는 작업은 [다른 필터들1] → SessionRepositoryFilter → [다른 필터들2] → DispatcherServlet → [다른 필터들2] → SessionRepositoryFilter → [다른 필터들1]

SessionRepositoryFilter(SessionRepositoryFilter.commitSession())에서 일어남(session 호출을 해야지만 생김)

ETC

  • rfc 형식 맞춰서 응답을 보내주면 브라우저가 알아서 해석해준다.
  • css,js 등 컨텐츠 타입도 맞춰줘야 제대로 브라우저가 해석해줌
  • 성능 최적화
    • GZIP
  • HTTP 캐싱
    • ETAG
    • last modified

0개의 댓글