3월 4주차 스터디 발표 자료📖
오늘은 입출력 스트림의 개념에 대해 알아보겠습니다!!
입출력 스트림 (I/O Stream)
- 자바에서는 파일이나 콘솔의 입력을 직접 다루지 않고 스트림(Stream)을 통해 다룬다.
즉, 입출력 스트림을 통해 입출력 장치와 자바 응용프로그램을 연결하여 데이터를 읽고 쓴다. - 🤔 입출력 스트림에서의 스트림은 무엇을 의미할까?
- 자바 8에서 등장하는 함수형 프로그래밍 방식의 반복자와는 다른 의미이다.
- 스트림이란 실제 입력이나 출력이 표현된 데이터의 연속적인 흐름이며 단방향성을 가진다.
- 스트림은 단방향성을 띄기때문에 입력과 출력을 동시에 처리할 수 없다. 따라서 입력 스트림과 출력 스트림으로 구분하여 사용한다.
입출력 스트림 종류
사진 출처 : https://javaconceptoftheday.com/byte-stream-vs-character-stream-in-java/
- 자바의 입출력 스트림은 크게 바이트 기반 스트림(Byte Stream)과 문자 기반 스트림(Character)으로 구분할 수 있다.
- 바이트 스트림
- 바이트 기반 스트림은 문자, 그림, 영상 등 다양한 형태의 데이터를 주고 받을 수 있다.
- 자바에서 스트림은 기본적으로 바이트 단위로 데이터를 전송한다.
- 바이트 스트림의 처리 단위는 1byte이다.
- 문자 스트림
- 문자 기반 스트림은 오직 문자만 주고 받을 수 있도록 설계되었다.
- 16bit 유니코드 문자를 주고받는다.
- 문자 스트림의 처리 단위는 2byte이다.
바이트 기반 스트림
- 바이트 스트림은 InputStream 클래스와 OutputStream 클래스로 구성되어 있다.
바이트 스트림의 종류
입력 스트림 | 출력 스트림 | 입출력 대상 |
---|---|---|
FileInputStream | FileOuputStream | 파일 |
ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte 배열) |
PipedInputStream | PipedOutputStream | 프로세스 |
AudioInputStream | PipedOutputStream | 오디오 |
바이트 스트림의 보조 스트림
- 보조 스트림?
- 보조 스트림은 기존 스트림의 기능을 향상시키거나 추가 및 보완하기 위해 제공된다.
- 스트림을 먼저 생성한 후 보조 스트림을 생성해야한다.
입력 스트림 | 출력 스트림 | 입출력 대상 |
---|---|---|
BufferedInputStream | BufferedOutputStream | 버퍼를 이용한 입출력 성능 향상 |
DataInputStream | DataOutputStream | 기본형 단위로 데이터를 처리하는 기능 |
ObjectInputStream | ObjectOutputStream | 데이터를 객체단위로 읽고 쓰는데 사용 (직렬화/역직렬화) 네트워크 상에서 Java 객체를 전송할 때 사용 |
FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
1
2
3
FileInputStream fileInputStream = new FileInputStream("test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedInputStream.read();
InputStream
- Input Stream 클래스는 데이터 소스로부터 byte 단위로 데이터를 읽어올 때 사용된다.
- Input Stream은 모든 입력 스트림 클래스의 조상 클래스이며 다양한 메서드를 제공한다.
InputStream.java
InputStream이 제공하는 메서드(일부)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
public static InputStream nullInputStream() { return new InputStream() { private volatile boolean closed; private void ensureOpen() throws IOException { if (closed) { throw new IOException("Stream closed"); } } @Override public int available () throws IOException { ensureOpen(); return 0; } @Override public int read() throws IOException { ensureOpen(); return -1; } @Override public int read(byte[] b, int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); if (len == 0) { return 0; } ensureOpen(); return -1; } @Override public byte[] readAllBytes() throws IOException { ensureOpen(); return new byte[0]; } @Override public int readNBytes(byte[] b, int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); ensureOpen(); return 0; } @Override public byte[] readNBytes(int len) throws IOException { if (len < 0) { throw new IllegalArgumentException("len < 0"); } ensureOpen(); return new byte[0]; } @Override public long skip(long n) throws IOException { ensureOpen(); return 0L; } @Override public long transferTo(OutputStream out) throws IOException { Objects.requireNonNull(out); ensureOpen(); return 0L; } @Override public void close() throws IOException { closed = true; } }; }
InputStream이 제공하는 메서드
- abstract int read()
- 입력 스트림으로부터 1바이트를 읽어서 int 값으로 반환한다.
- int read(byte[] b, int off, int len)
- 입력 스트림으로부터 len 만큼 읽어서 byte[] b의 off 위치에 저장하고 읽은 바이트 수를 반환한다.
- int read(byte[] b)
- 입력 스트림으로부터 인자로 주어진 바이트 배열의 크기만큼 데이터를 읽어 저장하고 읽은 바이트 수를 반환한다.
- int available()
- 현재 입력 스트림에서 읽을 수 있는 바이트 수를 반환한다.
- void close()
- 입력 스트림과 관련된 자원을 반납하고 종료한다.
- void mark(int readlimit)
- 입력 스트림의 현재 위치를 표시한다.
- void reset()
- mark() 메서드가 마지막으로 호출된 시점으로 이동한다.
- boolean markSupported()
- 입력스트림이 mark() , reset() 메서드를 지원하는지 확인한다.
- long skip(long n)
- 입력 스트림에서 n 바이트의 데이터를 건너뛰고 실제로 건너뛴 바이트 수를 반환한다.
OutputStream
- Output Stream 클래스는 데이터 소스로부터 byte 단위로 데이터를 특정 대상으로 출력하는 데 사용된다.
- OutputStream은 모든 출력 스트림 클래스의 조상 클래스이며 다양한 메서드를 제공한다.
OutputStream.java
OutputStream이 제공하는 메서드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
public abstract class OutputStream implements Closeable, Flushable { public static OutputStream nullOutputStream() { return new OutputStream() { private volatile boolean closed; private void ensureOpen() throws IOException { if (closed) { throw new IOException("Stream closed"); } } @Override public void write(int b) throws IOException { ensureOpen(); } @Override public void write(byte b[], int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); ensureOpen(); } @Override public void close() { closed = true; } }; } public abstract void write(int b) throws IOException; public void write(byte b[]) throws IOException { write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { Objects.checkFromIndexSize(off, len, b.length); // len == 0 condition implicitly handled by loop bounds for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } public void flush() throws IOException { } public void close() throws IOException { } }
OutputStream이 제공하는 메서드
- write(int b)
- b의 1바이트 만큼만 OutputStream에 쓴다(=출력한다).
- write(byte[] b)
- 인자로 주어진 바이트 배열의 모든 바이트를 OutputStream에 쓴다.
- void write(byte[] b, int off, int len)
- 인자로 주어진 바이트 배열의 off 위치부터 len 크기 만큼의 바이트를 outputStream에 쓴다.
- close()
- 출력 스트림과 관련된 자원을 반납하고 종료한다.
- flush()
- OutputStream 내부 버퍼에 남아있는 데이터를 모두 출력한다.
- Stream의 내부 Buffer란 출력데이터를 저장하기 위한 일시적인 메모리 영역이다.
- OutputStream은 기본적으로 내부에 Buffer를 가지고있지 않아 OutputStream에 쓰면 바로 출력 대상에 출력이 된다.
- 🤔 OutputStream이 내부에 Buffer를 가지고 있지 않다면 언제 flush 메서드를 사용하는걸까?
- BufferedOutputStream 같이 내부에 Buffer를 가질 수 있는 하위 클래스를 사용하면 내부 Buffer를 사용할 수 있다.
- 내부 Buffer를 사용하면 Buffer가 꽉찰 때나 프로그램이 종료될 때 데이터를 한번에 출력하도록 하여 출력 횟수를 줄일 수 있다 → 성능 개선
문자 기반 스트림
- 바이트 기반의 스트림은 단순히 1 byte 단위로 데이터를 처리하기 때문에 2 byte 문자를 다루는 데 한계가 있다. 이러한 한계를 보완하기 위해 등장한 것이 문자 기반 스트림이다.
- 문자 기반 스트림은 내부적으로 유니코드를 바이트 배열로 변환하고 이를 다시 지정된 인코딩 방식으로 변환하여 입출력한다.
- 바이트 스트림은 byte[]를 사용한다면 문자 기반 스트림은 char[]를 사용한다.
문자 기반 스트림의 종류
Reader | Writer | 입출력 대상 |
---|---|---|
FileReader | FileWriter | 파일 |
CharArrayReader | CharArrayWriter | 메모리(byte 배열) |
PipedReader | PipedWriter | 프로세스 |
StringReader | StringWriter | 문자열 |
문자 기반 스트림의 보조 스트림
Reader | Writer | 입출력 대상 |
---|---|---|
BufferedReader | BufferedWriter | 버퍼를 이용해 입출력 성능 향상 |
InputStreamReader | OutputStreamWriter | 바이트 기반 스트림을 문자 기반 스트림으로 변경 바이트 기반 스트림 데이터를 지정된 인코딩 문자 데이터로 변환 |
- Scanner와 BufferReader
- 우리는 흔히 문자를 입력받을 때 Scanner나 BufferReader를 사용한다. 이 둘의 차이는 뭘까?
- Scanner는 BufferReader에 비해 속도가 느리다.
- Scanner의 경우는 1KB의 버퍼를 갖고 BufferReader는 8KB의 버퍼를 갖는다. 따라서 문자열을 크기가 커지면 BufferReader의 출력 횟수가 더 적어지기 때문에 속도가 더 빠르다.
Reader
- 문자 기반 입력 스트림의 최상위 클래스이며 모든 문자 기반 입력 스트림은 Reader 클래스를 상속한다.
- 다양한 입력 소스를 처리할 수 있다.
- Reader가 제공하는 메서드는 InputStream에서 제공하는 메서드와 char[] 배열을 사용한다는 것을 제외하면 다르지 않아 생략한다!
Writer
- 문자 기반 출력 스트림의 최상위 클래스이며 모든 문자 기반 출력 스트림은 Writer 클래스를 상속한다.
- 다양한 출력 대상을 처리할 수 있다.
Writer가 제공하는 주요 메서드
- Writer가 제공하는 메서드는 Reader와 마찬가지로 OutputStream에서 제공하는 메서드와 char[] 배열을 사용한다는 것을 제외하면 크게 다르지 않다. 따라서 OutputStream의 메서드와 다른 일부 메서드만 정리한다.
- Writer append(char c)
- Writer에 문자를 추가한다.
- Writer append(CharSequence csq)
- Writer에 CharSequence를 추가한다.
- Writer append(CharSequence csq, int start, int end)
- Writer에 CharSequence의 start부터 end의 문자까지 추가한다.
- 🤔 append의 반환값은 왜 Writer일까?
- Writer 객체를 반환하면 동일한 문자 스트림에 대해 여러번 append() 메소드를 호출하여 여러 문자열을 연속적으로 추가할 수 있다 (메소드 체이닝)
→ 가독성과 효율성이 높아진다.
1 2 3 4 5 6 7
// 메소드 체이닝 writer.append("Hello, ").append("world!").append("\n"); // 메소드 체이닝 x writer.append("Hello, "); writer.append("world!"); writer.append("\n");
- Writer 객체를 반환하면 동일한 문자 스트림에 대해 여러번 append() 메소드를 호출하여 여러 문자열을 연속적으로 추가할 수 있다 (메소드 체이닝)