본문 바로가기

언어/Java

입출력 스트림(InputStream, OutputStream), 버퍼(Buffer)의 개념

백준의 알고리즘에서 입출력 관련 문제를 풀 때 항상 Scanner 사용법만 외워서 입력받아 문제를 풀면서

Buffer와 Stream의 정확한 개념에 대해 공부할 필요성을 느꼈다.

Scanner의 경우에 스페이스와 개행 문자를 통하여 입력 값을 인식하므로 따로 가공할 필요가 없지만,

BufferedReader의 경우에는 엔터만 경계로 인식하고 받은 데이터가 String으로 고정되기 때문에 데이터를 따로 가공해야 한다. 그러나 Scanner에 비하면 상대적으로 빠르기 때문에 많은 데이터를 입력받아야 하는 상황에서는 BufferedReader를 이용하는 것이 더 효율적이다.

모든 것을 한 번에 알기엔 많은 내용이라 차근차근 정리해가며 공부해보자

 

스트림(Stream)이란?

  • FIFO구조
  • 단방향
  • 지연(Blocking)될 수 있다.
  • 바이트 단위의 집합

자바는 입력출력 직접 다루지 않고, 스트림(Stream)이라는 흐름을 통해 다룬다. 스트림의 사전적으로 '흐르다' 라는 의미를 가지고 있는데 데이터(data)와 물의 흐름과 같다는 의미로 사용되었다.

물이 한쪽 방향으로 흐르는 것처럼 스트림은 단방향 통신만 가능하여 하나의 스트림으로 입출력을 동시에 처리할 수 없다. 기본적으로 바이트(Byte) 형태로 데이터를 운반하는데 사용되며 스트림(Stream) 데이터는 순차적으로 흐르고 먼저 입력된 데이터가 먼저 출력되는 큐(queue)의 FIFO(First in Frist Out)구조로 되어있다. 

그리고 이전 스트림(Stream)을 처리하는 쓰레드(Thread)의 작업이 끝나지 않으면 다음 스트림에 대한 처리가 지연(Blocking)될 수 있다.

 

입력을 위한 Input Stream과 출력을 위한 Output Stream이 존재한다. inputStream 은 스트림을 한 줄 씩 읽고, outputStream으로 데이터를 내보낸 다음 해당 공간을 비운다.

 

inputStream

바이트 기반 입력 스트림의 최상위 추상클래스로 모든 바이트 기반 입력 스트림은 inputStream 클래스를 상속받아서 만들어 진다.

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

 

outputStream

바이트 기반 출력 스트림의 최상위 추상클래스로 모든 바이트 기반 출력 스트림은 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바이트를 출력

 

inputStream, outputStream 예

  • InputStream, OutputStream을 사용하기 위해서 import를 진행해야 한다.
  • InputStream으로 입력 받는 경우에는 1개의 문자만 int형으로 받음
  • 출력하기 위해서 out.write()를 한 후에 flush()와 close()를 사용
    • flush() : 현재 버퍼에 저장되어 있는 내용을 클라이언트로 전송하고 버퍼를 비운다. 버퍼가 모두 채워지기 전에 프로그램을 종료하면 버퍼에 있는 내용은 쓰여지지 않아서 flush()를 호출하여 출력 바이트를 강제로 쓰이게 한다.
    • close() : 스트림을 닫는다. close() 후 해당 스트림을 다시 이용하여 출력이 불가능하다. 작업이 끝나고 나면 스트림을 닫아 메모리를 확보해야 한다.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Main {
	public static void main(String[] args) throws IOException{
		InputStream is = System.in;
		OutputStream os = System.out;
		
		int idata = is.read();
		
		os.write(idata);
		os.flush();
		os.close();
	}
}

 

 

 

InputStreamReader, OutputStreamWriter

InputStream과 OutputStream으로 하나의 값만 출력이 가능했는데 이러한 단점을 보완하기 위하여 InputStreamReader와 OutputStreamWriter을 사용하여 여러 값을 출력할 수 있다. 

  • InputStreamReader : 바이트 단위로 읽어 들이는 InputStream을 이용하여 데이터를 문자 단위로 읽어 들인다.
  • OutputStreamWriter : 바이트 단위로 쓰는 OutputStream을 이용하여 데이터를 문자 단위로 쓸 수 있음

 

InputStreamReader 메서드

메서드 설명
int read() 문자 하나 읽음(읽을 문자가 없는 경우 -1 리턴)
int read(char[] cbuf, int off, int len) cbuf의 버퍼에 off부터 len 길이만큼의 문자를 읽음
void close() InputStreamReader를 닫음
String getEncoding() 현재 사용하는 문자 인코딩의 표준 이름을 얻음
boolean ready() InputStream에서 문자가 있는지 읽을 수 있는지 여부를 확인

 

 

OutputStreamWriter 메서드

메서드 설명
void close() OutputStreamWriter를 닫음
void flush() OutputStreamWriter의 버퍼를 비운다. (출력)
void write(int c) c개의 문자를 쓴다.
void write(char[] cbuf, int off, int len) cbuf의 버퍼에 off부터 len만큼을 담아 문자를 쓴다.
void write(String str, int off, int len) 문자열 str의 off부터 len만큼을 담아 문자를 쓴다.
String getEncoding() 현재 사용하고 있는 문자 인코딩의 표준 이름을 얻는다.

 

 

InputStreamReader, OutputStreamWriter 예

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Main {
	public static void main(String[] args) throws IOException{
		InputStreamReader isr = new InputStreamReader(System.in);
		OutputStreamWriter osw = new OutputStreamWriter(System.out);
		
		char cdata[] = new char[2] ;
		isr.read(cdata);
		
		osw.write(cdata);
		osw.flush();
		osw.close();
		
	}
}

 

 

위의 예시처럼 InputStreamReader는 고정된 값밖에 가져올 수 없다. 다시 말해서 고정된 값보다 큰 데이터일 경우에 공간이 부족하고 고정된 값보다 작은 데이터일 경우에는 공간의 낭비가 생긴다. 

 

 

버퍼(Buffer)

InputStream의 경우 하나의 값만 가져올 수 있고 InputStreamReader의 경우에는 고정된 값만 가져올 수 있다. 이 둘을 보완한 것이 바로 Buffer이다.

버퍼는 문자는 묶어서 한 번에 전달하게 된다. 그래서 전송 시간이 더 적게 걸리고 성능이 향상된다 즉, 버퍼는 입출력을 수행할 때 속도 차이를 극복하기 위해서 일시적으로 데이터를 보관하는 임시 저장 공간을 의미한다.

또한, 버퍼는 고정된 값이 아닌 가변적인 값을 받는다. 그래서 고정된 값을 받았던 InputStreamReader보다 더 효율적으로 메모리를 사용할 수 있다.

위의 그림처럼 입력된 값을 버퍼에 저장해두고 개행 문자가나 버퍼가 가득차면 버퍼의 내용을 한 번에 전송한다.

 

 

BufferedReader

메서드 설명
void close() 입력스트림을 닫고 사용하던 자원들을 풀어준다.
void mark(int, readAheadLimit) 스트림의 현재 위치를 마킹한다.
boolean markSupported() 스트림이 mark 기능을 지원하는지 확인
int read() 한 글자만 읽어 정수형으로 반환
int read(char[] cbuf, int off, int len) cbuf의 off 위치부터 len 길이만큼 문자를 스트림으로부터 읽어 온다.
String readLine() 한 줄을 읽어서 String으로 반환
boolean ready() 입력스트림이 사용할 준비가 되어있는지 확인
void reset() 마킹이 있다면 그 위치부터 다시 시작, 그렇지 않으면 처음부터 다시 시작
long skip(long n) n개의 문자를 건너뛴다.

 

 

BufferedWriter

메서드 설명
void close() 스트림을 닫음
void flush() 스트림을 비운다.
void newLine() 개행 역할을 해준다.
void write(char[] cbuf, int off, int len) 버퍼 off 위치부터 len의 크기만큼 write
void write(int c) 한 글자를 쓴다.
void write(String s, int off, int len) 문자열 s를 off부터 일정 길이만큼 write

 

 

BufferedReader, BufferedWriter 예

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class Main {
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in) );
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		String s = br.readLine();
		br.close();
		
		bw.write(s);
		bw.flush();
		bw.close();
	}
}