입출력은 하나의 시스템에서 다른 시스템으로 데이터를 이동시킬 때 사용한다.
자바는 스트림으로부터 I/O를 사용한다.(InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.)
스트림은 데이터를 바이트로 읽고 쓴다. 따라서 바이트가 아닌 문자를 읽고 쓰기 위해서는 Reader와 Writer라는 클래스를 사용해야한다.
// 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]);
}
}
}
ByteArrayOutputStream: 메모리의 바이트 배열에 데이터를 쓴다.
BufferedOutputStream: 입출력 작업을 효율적으로 만들기 위해 다른 기본 스트림을 래핑하고 기본 스트림에 대한 버퍼링을 제공한다.
BufferedOutputStream은 ByteArrayOutputStream의 기능을 보완해준다. 자체적인 입출력 기능은 없으며, 입출력 기능은 기반 스트림에 위임한다. 따라서 생성시 OutputStream을 인자로 받는다.
버퍼링을 사용하려는 이유
큰 데이터 블록을 파일 시스템에 쓰는 것이 바이트 단위로 쓰는 것보다 더 효율적이기 때문. 버퍼에 데이터 조각들을 수집한 다음 한 번에 디스크에 쓰는 것이 효율적
효율적인 전송을 위해 BufferedOutputStream 필터를 연결하여 스트림에서 버퍼링 사용 가능
버퍼링을 사용하면 OutputStream을 사용할 때 flush 메서드 사용(버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송. 스트림은 동기로 동작하기 때문에 버퍼가 찰 때까지 기다리면 데드락 상태가 되기 때문에 flush로 해제)
String은 byte[]를 인자로 받아 문자열을 생성해줄 수 있다. InputStream의 readAllBytes 메서드(intput stream의 모든 바이트들을 읽는 메서드)의 반환값이 byte[]이므로 new String(inputStream.readAllBytes());
이런식으로 바이트로 반환한 값을 문자열로 바꿀 수 있다.
readAllBytes가 아닌 일반 read 메서드는 단일 바이트를 읽어서 0 ~ 255 사이의 int값을 반환한다.(int 값을 byte로 변화하면 -128 ~ 127의 값으로 변환되며 스트림의 끝에 도달 시 -1을 반환한다)
스트림 사용 후엔 close 메서드 사용하여 스트림을 닫아주는 것이 좋다. 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스 누수가 발생한다. try-with-resource 구문을 사용해서 자동으로 자원을 반납하도록 해도 된다.
@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 클래스를 사용하여 파일을 읽어서 사용자에게 전달해준다.
클래스를 로딩하기 위해서는 class 파일을 바이트로 읽어 메모리에 로딩해야한다. 클래스 패스에 존재하는 모든 클래스 파일들(그림 파일, 사운드 파일, 설정 파일 등등 모든 파일들)은 클래스로더를 통해 찾을 수 있다.
클래스로더: 바이트 코드를 읽어들여 class 객체를 생성하는 역할. 클래스를 로딩하는 시점은 런타임시.(참고: [Java] JVM, JRE, JDK - 클래스 로더 시스템 부분)
그럼 클래스로더는 어떻게 가져올까? 각 스레드마다 다른 클래스 로더를 사용할 수 있기 때문에 현재 스레드를 찾아서 클래스로더를 가져와야한다.
final URL url = Thread.currentThread().getContextClassLoader().getResource(fileName);
final String path = url.getPath();
참고
웹에서 사용하는 URL에 관한 정보를 추상화 한 클래스
URL을 일반화 시킨 클래스. 순수하게 리소스를 식별하고 URI를 분석하는 기능만 제공. URI가 참조하는 리소스를 가져오는 메서드는 제공하지 않음.
URL URI 클래스 차이
https://codedragon.tistory.com/5499
File 클래스의 업그레이드 버전. 경로를 조작하는데 사용.
Path 클래스를 생성하는데 사용하는 헬퍼클래스
파일이나 폴더에 대한 제어를 할 때 사용.
파일을 손쉽게 다룰 수 있는 유틸성 메서드를 모아둔 클래스.
병렬 작업시 여러 작업을 효율적으로 처리하기 위해 제공되는 자바 라이브러리. 작업을 분리하고 수행하는 작업을 직접 구현하려면, 각각 스레드를 생성하여 작업을 처리하고, 처리 완료시 해당 스레드를 제거하는 작업을 해줘야한다. 이런 작업을 쉽게 처리하게 해주는 클래스이다. ThreadPool을 구현하기가 매우 쉽다.
참고
자바에서 제공하는 Concurrency(병행성)에 관련된 API. 스레드를 N개 실행했을 때, 일정 개수의 스레드가 모두 끝날때까지 기다려야 다음 작업을 진행할 수 있거나 다른 스레드를 실행시킬 수 있는 경우에 사용한다.
CountDownLatch 초기화 시 int값 count를 넣어준다. 스레드는 작업 마지막에 CountDownLatch의 countDown() 메서드를 호출한다. 그러면 초기화 시 넣어줬던 count값이 감소한다. 즉, 각 스레드는 작업 완료를 countDown() 메서드로 알려준다. 스레드의 작업이 끝나길 기다리는 쪽에서는 CountDownLatch의 await() 메서드를 호출한다. 그러면 현재 메서드가 실행중인 메인스레드는 진행을 멈추고, CountDownLatch의 count가 0이 될 때까지 기다린다.
참고
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 호출을 해야지만 생김)