[Java] I/O
본문 바로가기
Java

[Java] I/O

by IYK2h 2023. 7. 22.
728x90

I/O

목차

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

I/O 입출력

입출력이란?

입출력(I/O)란 Input과 Output의 약자로 입력과 출력, 간단히 입출력이라 한다.

입출력은 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고받는 것을 말한다.

자바 NIO(New I/O)

자바 1.4 버전부터 추가된 API로 넌블로킹 처리가 가능하며, 스트림이 아닌 채널을 사용한다.

채널기반의 입출력방식을 사용하며 단방향이 아닌 양방향으로 입출력이 가능하다.

IO vs NIO

  • IO의 방식으로 각각의 스트림에서 read()와 write()가 호출이 되면 데이터가 입력되고, 데이터가 출력되기 전까지, 스레드는 블로킹(멈춤) 상태가 된다. 이렇게 되면 작업이 끝날 때까지 기다려야 하며, 그 이전에는 해당 IO 스레드는 사용할 수 없게 되고, 인터럽트도 할 수 없다. 블로킹을 빠져나오려면 스트림을 닫는 방법 밖에 없다.
  • NIO의 블로킹 상태에서는 Interrupt를 이용하여 빠져나올 수 있다.
    구분 IO NIO
    입출력 방식 스트림 채널
    버퍼 방식 Non-Buffer Buffer
    비동기 방식 지원 X O
    Blocking/Non-Blocking 방식 Blocking Only Both
    사용 케이스 연결 클라이언트가 적고 IO 가 큰 경우 연결 클라이언트가 많고 IO 처리가 작은 경우

스트림 (Stream)

FIFO(First In First Out)

단방향이기 때문에 입력 스트림과 출력 스트림을 별도로 사용해야 한다.

연속된 데이터의 흐름으로 입출력 진행 시 다른 작업을 할 수 없는 블로킹상태가 된다.

입출력 대상을 변경하기 편하며 동일한 프로그램 구조를 유지할 수 있다.

버퍼 (Buffer)

  • byte, char, int 등 기본 데이터 타입을 저장할 수 있는 저장소로서, 배열과 마찬가지로 제한된 크기(capacity)에 순서대로 데이터를 저장한다.
  • 버퍼는 데이터를 저장하기 위한 것이지만, 실제로 버퍼가 사용되는 것은 채널을 통해서 데이터를 주고받을 때 쓰인다.
  • 채널을 통해서 소켓, 파일 등에 데이터를 전송할 때나 읽어올 때 버퍼를 사용하게 됨으로써 가비지량을 최소화할 수 있게 되며, 이는 가바지 컬렉션 회수를 줄임으로써 서버의 전체 처리량을 증가시켜 준다.

채널 (Channel) 기반의 I/O

  • 데이터가 통과하는 쌍방향 통로이며, 채널에서 데이터를 주고받을 때 사용되는 것이 버퍼이다.
  • 채널에는 소켓과 연결된 SocketChannel, 파일과 연결된 FileChannel, 파이프와 연결된 Pipe.SinkChannel과 Pipe.SourceChannel 등이 존재하며, 서버소켓과 연결된 ServerSocketChannel 도 존재한다.

InputStream과 OutputStream

InputStream과 OutputStream은 Java Stream의 부모들이다

InputStream

  • 바이트 기반의 입력 스트림의 최상위 클래스(추상 클래스)이다.
  • 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다.
  • 버퍼, 파일, 네트워크 단에서 입력되는 데이터를 읽어오는 기능을 수행한다.

메소드들

  • int available() : 현재 읽을 수 있는 바이트 수를 반환
  • void close() : 현재 열려있는 InputStream을 닫는다
  • void mark(int readlimit) : InputStream에서 현재의 위치를 표시
  • boolean markSupported() : 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인
  • abstract int read() : InputStream에서 한 바이트를 읽어서 int 값으로 반환
  • int read(byte[] b) : 인자만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수 반환
  • int read(byte b, int off, int len) : len만큼 읽어서 b의 off위치에 저장하고 읽은 바이트 수 반환
  • void reset() : mark()를 마지막으로 호출한 위치로 이동
  • long skip(long n) : InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환

OutputStream

  • 바이트 기반의 입력 스트림의 최상위 클래스(추상 클래스)이다.
  • 모든 바이트 기반 출력 스트림은 이 클래스를 상속받아서 만들어진다.
  • 버퍼, 파일, 네트워크 단으로 데이터를 내보내는 기능을 수행한다.

메소드들

  • void close() : OutputStream을 닫는다
  • void flush() : 버퍼에 남아있는 출력 스트림을 출력
  • void write(byte[] b) : 버퍼의 내용을 출력
  • void write(byte[] b, int off, int len) : b 배열 안에 있는 시작 off부터 len만큼 출력
  • abstract void write(int b) : 정수 b의 하위 1바이트를 출력

Byte와 Character 스트림

앞서서 살펴본 InputStream과 OutputStream 은 대표적인 ByteStream이다. ByteStream 은 이름처럼 1바이트 단위로 데이터를 입출력한다.
그에 반해 CharacterStream 은 2바이트 단위로 데이터를 전송하는데, 자바는 기본적으로 character 값들이 유니코드 규약에 맞춰져 있다. 그리고 유니코드는 기본단위가 2바이트임으로 문자를 입출력할 때, CharacterStream을 쓰는 것이 가장 적절하다고 할 수 있다.
CharacterStream 은 클래스명에 Reader, Writer 가 들어가 있다.

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

표준 스트림

프로그램과 환경(키보드, 모니터, 콘솔..) 사이에 연결된 입출력 스트림을 의미한다.
Java 컨텍스트에서 표준 입력/출력 스트림(System.in, System.outSystem.err)은 JVM(Java Virtual Machine)과 연결되며 JVM이 실행 중일 때 자동으로 사용 가능하다. 초기화 프로세스 중에 JVM에 의해 열리고 구성되며 명시적으로 만들 필요가 없다.

System.in

콘솔로부터의 입력을 받는 표준 스트림을 가리키는 상수이다. 해당객체는 JVM 이 메모리에 올라갈 때 생성된다고 한다. InputStream으로 byte 단위로 입력을 받을 수 있다.

System.out.print("입력 : ");
int input = System.in.read();
System.out.println("출력 : " + (char)input);
// ----
입력 : h
출력 : h
// ---- 
입력 : 안
출력 : ì

InputStream 은 ByteStream으로 한글을 입력받는 데 있어서 어려움이 있다.

한글은 유니코드로 2byte 단위이기 때문에 InputStream으로는 1바이트 밖에 읽지 못한다.

이 경우에는 CharacterStream 인 InputStreamReader을 활용할 수 있다.

System.out.print("입력 : ");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
int input = inputStreamReader.read();
System.out.println("출력 : " + (char)input);
// ----
입력 : 안
출력 : 안

만약 2byte가 넘는 데이터를 읽고 싶다면, BufferedReader를 활용할 수도 있다.

System.out.print("입력 : ");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String inputData = bufferedReader.readLine();
System.out.println("출력 : " + inputData);
// ----
입력 : 안녕하세요
출력 : 안녕하세요

혹은, Scanner를 사용할 수 도 있다.

System.out.print("입력 : ");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.println("출력 : " + inputData);
// ----
입력 : 안녕하세요
출력 : 안녕하세요

System.out

콘솔로 출력을 하는 표준 스트림을 가리키는 상수이다.
대표적인 예시 System.out.println()

System.err

표준 에러 출력 장치를 가리키는 상수이다

파일 읽고 쓰기

  • 텍스트 파일인 경우 문자 스트림 클래스들을 사용하면 되고, 바이너리 파일인 경우 바이트 스트림을 기본적으로 사용한다.
  • 입출력 효율을 위해 Buffered 계열의 보조 스트림을 함께 사용하는 것이 좋다.
  • 텍스트 파일
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String s;
while ((s = br.readLine()) != null) {
bw.write(s + "\n");
}
  • 이진 파일
BufferedInputStream is = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("b.jpg"));
byte[] buffer = new byte[16384];
while (is.read(buffer) != -1) {
os.write(buffer);
}

 

Reference

https://www.notion.so/I-O-af9b3036338c43a8bf9fa6a521cda242

728x90

'Java' 카테고리의 다른 글

[Java] 람다식  (0) 2023.09.25
[Java] 제네릭(Generic)  (0) 2023.08.07
[Java] 어노테이션(Annotation)  (0) 2023.07.04
[Java] 리플렉션(Reflection)  (0) 2023.06.30
[Java] 바이트코드 조작  (0) 2023.06.23

댓글