개발 공부 기록하기/01. JAVA & Kotlin

Java InputStream이란?

lannstark 2020. 8. 21. 23:17

InputStream OutputStream을 실무에서 사용할 때면, 뭔가 알긴 알고 실제로 둘을 활용해 기능을 구현하는데는 전혀 문제가 없지만, 사용할때마다 찾아보게되고 뭔가 정확히 아는 것 같지는 않다라는 느낌적인 느낌이 있는 친구들이다.

그래서 이번 기회에 화끈하게 InputStream, OutputStream을 파보려고 한다.

InputStream의 정의

JDK 11 기준 InputStream의 설명을 읽어보면

This abstract class is the superclass of all classes representing an input stream of bytes

라고 되어 있다. 중요한 부분은 "representing an input stream of bytes"이다. 하나씩 살펴보자.

Stream

먼저 Stream을 위키백과에 검색하면 이렇게 나온다.

개별 바이트나 문자열인 데이터의 원천
파일을 읽거나 쓸 때, 네트워크 소켓을 거쳐 통신할 때 쓰이는 추상적인 개념

백과에 정의된 내용을 쉽게 표현하면, '데이터가 전송되는 통로'라고 표현할 수 있다.

데이터가 네트워크를 거치건, 파일에서 넘어오건, 키보드로부터 오건, 데이터가 오고가는 통로가 스트림인 것이다.

byte

바이트란 이해하기 쉽다. 다 알고 계시겠지만 모든 데이터는 결국 0과 1로 귀결되고 0이나 1이 8개 모이면 그것을 'byte'라고 부른다. 따라서 우선은 그냥 '데이터'라고 생각해도 된다.

종합하면 InputStream 추상 클래스는 데이터가 들어오는 통로의 역할에 관해 규정하고 있는 추상 클래스이다.

아 역시 뭔가 또 막상 생각을 하면 어려울게 없다.

InputStream 주요 메소드

그럼 이제 데이터가 들어오는 통로는 어떤 역할을 수행해야 하는지 주요 메소드를 알아보자!

InputStream은 데이터를 읽어야 한다

파일에서 오건, 메모리에서 오건, 네트워크에서 오건 통로로 데이터를 빨아들이는 기능이 필요하다!
데이터를 읽는 기능과 관련된 메소드는 3개가 있다.

/**
 * 1byte를 읽어 그 값을 int로 바꿔 반환하거나 (1byte = -128 ~ 127 또는 0 ~ 255이다)
 * 더 이상 읽을 수 없는 경우는 -1을 반환한다
 */
public abstract int read() throws IOException;

// Main method
byte[] data = new byte[]{1, 2};
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);

System.out.println(inputStream.read()); // 바이트 하나를 읽었으니, 1을 읽었고 1이 출력된다
System.out.println(inputStream.read()); // 바이트 하나를 추가로 읽었으니, 2를 읽었고 2가 출력된다
System.out.println(inputStream.read()); // 더 이상 읽을 바이트가 없으니 -1이 출력된다
/**
 * 1byte씩 읽는게 아니라 파라미터로 주어진 byte 배열 크기만큼 데이터를 읽고 총 몇 byte를 읽었는지 반환한다.
 * 데이터를 읽다가 모두 읽으면, 그 만큼의 크기를 반환하고
 * 시작부터 읽을게 없으면 -1을 반환한다
 */
public int read(byte b[]) throws IOException { }

// Main method
byte[] data = new byte[]{10, 20, 30, 40, 50};
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);

byte[] buffer = new byte[3];
System.out.println(inputStream.read(buffer)); // 바이트 3개(10, 20, 30)을 읽어 buffer에 넘겨주고 3을 출력한다
System.out.println(inputStream.read(buffer)); // 바이트 2개(40, 50)을 읽어 buffer에 넘겨주고 2를 출력한다
System.out.println(inputStream.read(buffer)); // 시작부터 읽을게 없으니 -1을 출력한다
/**
 * len개의 byte를 읽어서 주어진 byte[] b의 b[off]부터 저장한다
 * 저장한 개수를 반환하고, 읽을게 더 이상 없다면 -1을 반환한다
 *
 * 만약 bytep[] 크기가 부족하다면 IndexOutOfBoundsException을 낸다
 */
public int read(byte[] b, int off, int len) throws IOException { }

여기까지 읽었다면 InputStream에 대해 한 가지 알아낼 수 있다.

  • InputStream은 말 그대로 통로이기 때문에, (read만 사용해서는) 한 번 읽었던 것을 다시 되돌아가 읽을 수 없다.

InputStream은 데이터를 스킵할 수 있다

/**
 * n byte의 데이터를 스킵하고 실제로 스킵한 byte 개수가 출력된다
 * 지원하지 않을 경우 IOException이 나오게 된다
 */
public long skip(long n) throws IOException { }

// Main method
byte[] data = new byte[]{10, 20, 30, 40, 50};
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);

System.out.println(inputStream.skip(3)); // 3개 skip 성공, 3을 출력
System.out.println(inputStream.read()); // 위에서 10, 20, 30을 skip했으므로 40을 출력

InputStream은 데이터가 얼마나 남았는지 알려준다

public int available() throws IOException;

InputStream은 닫을 수 있다

통로를 부실 수 있는 것이다

public void close() throws IOException;

InputStream은 읽었던 데이터를 특정 시점부터 다시 읽을 수 있다

/**
 * 특정 시점을 기록하는 메소드 : mark
 * readlimit은 마킹할 위치를 기록하는 것이 아니라,
 * 현재 위치를 마킹하고나서 최대 몇개의 byte를 더 읽을 수 있는지를 의미한다
 *
 * 예를 들어, readlimit을 100으로 설정했다면,
 * 지금 mark를 호출하고 read()를 101번 호출 할 수 없는 것이다.
 */
public synchronized void mark(int readlimit) { }
/**
 * mark되어 있는 지점으로 돌아간다
 * 이 이후에 read를 하면 아까 mark 해두었던 시점부터 데이터를 읽어들이는 것이다.
 */
public syncrhonized void reset() throws IOException { }
/**
 * 이 InputStream 구현체가 mark / reset을 지원하는지, 지원하지 않는지를 표기한다.
 */
public boolean markSupported()

정리하면

아 드디어 모든 것(?)을 깨달았다.

InputStream은 데이터를 byte 단위로 읽어들이는 통로이며 (읽어들인 데이터를 byte로 돌려줌)

InputStream이 갖춰야 할 덕목으로는

  • 데이터 읽기
  • 특정 시점으로 되돌아가기
  • 얼마나 데이터가 남았는지 보여주기
  • 통로 끊기

가 있다! (물론 하위 구현체마다 지원하지 않는 덕목이 있을 수 있다)

끝!