자바의 Input과 Output에 대해 학습한다.
컴퓨터 내부 또는 외부 장치와 프로그램간의 데이터를 주고 받는 것
예를 들면 키보드로부터 데이터를 입력받거나, System.out.println()
로 콘솔에 데이터를 출력하는 것
자바에서 어느 한쪽에서 다른 쪽으로 데이터를 전달하기 위해, 두 대상을 연결하고 데이터를 전송할 수 있게 하는 것이다.
즉, 데이터를 전달하는데 사용되는 연결 통로이며, 연속적인 데이터의 흐름을 물에 비유하여 Stream이라는 명칭이 붙여졌다.
💡 Java 8 버전부터 추가된 스트림 API는 앞서 설명한 스트림과는 전혀 다른 개념이다.
입출력하는 데이터 단위에 따라 바이트 기반 스트림(1byte)과 문자 기반 스트림(2byte)으로 나뉜다.
바이트 기반 스트림
입출력하는 데이터 단위가 1byte이다.
문자 기반 스트림
입출력하는 데이터 단위가 2byte이다.
바이트 기반 스트림은 바이트 단위로 데이터를 전송한다.
모든 바이트 기반 입력 스트림의 조상은 InputStream
이고,
모든 바이트 기반 출력 스트림의 조상은 OutputStream
이다.
바이트 기반 입출력 스트림은 입출력 대상에 따라 다음과 같이 종류가 나뉜다.
입출력 대상 | 입력 스트림 | 출력 스트림 |
---|---|---|
파일 | FileInputStream | FileOutputStream |
메모리(byte 배열) | ByteArrayInputStream | ByteArrayOutputStream |
프로세스(프로세스간 통신) | PipedInputStream | PipedOutputStream |
오디오 장치 | AudioInputStream | AudioOutputStream |
InputStream
과 OutputStream
의 메소드
InputStream
의 메소드
InputStream | 정의 |
---|---|
abstract int read() | 1byte를 읽어서 반환한다.(0 ~ 255사이의 값) - 더이상 읽어올 데이터가 없으면 -1을 반환한다. - 추상 메소드이므로 InputStream 의 각 자손들은 자신에 맞게 해당 메소드를 구현한다. |
int read(byte[] b) | 최대 바이트 배열 b의 크기 만큼 데이터를 읽어서 b에 저장한다. - 읽어 온 데이터의 수를 반환한다. - 내부적으로 read(byte[] b, int off, int len) 를 사용한다. |
int read(byte[] b, int off, int len) | 최대 len개의 byte를 읽어서 바이트 배열 b의 지정된 위치(off)에 저장한다. - 읽어온 데이터의 개수를 반환한다. - 내부적으로 read() 를 사용한다. |
void close() | 스트림을 닫아 사용하고 있던 자원을 반환한다. |
int available() | 스트림으로부터 읽어올 수 있는 데이터의 크기를 반환한다. |
void mark(int readlimit) | 현재위치를 표시해 놓는다. - 후에 reset() 에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다.- readlimit은 되돌아갈 수 있는 byte의 수이다. - mark() 기능을 지원하는 것은 선택적이다. |
void reset() | 스트림에서의 위치를 마지막으로 mark() 가 호출된 위치로 되돌린다.- reset() 기능을 지원하는 것은 선택적이다. |
boolean markSupported() | mark() 와 reset() 을 지원하는지를 알려 준다. |
long skip(long n) | 스트림에서 주어진 길이(n)만큼 건너뛴다. |
OutputStream
의 메소드
OutputStream | 정의 |
---|---|
abstract void write(int b) | 1byte를 출력한다. |
void write(byte[] b) | 주어진 바이트 배열 b에 저장된 모든 데이터를 출력한다. - 내부적으로 write(byte[] b, int off, int len) 를 사용한다. |
void write(byte[] b, int off, int len) | 주어진 바이트 배열 b의 off위치부터 len개의 byte를 출력한다. |
- 내부적으로 write() 를 사용한다. | |
void close() | 스트림을 닫아 사용하고 있던 자원을 반환한다. |
void flush() | 스트림의 버퍼에 있는 모든 내용을 출력한다. - 버퍼가 있는 출력 스트림에만 해당 함수를 사용할 수 있으며, OutputStream 의 flush() 는 아무 일도 하지않는다. |
🤔 read()의 반환 타입이 int인 이유
바이트 기반의 스트림이므로 데이터를 1byte(0 ~ 255) 읽어오고 읽어올 데이터가 없다면 -1을 리턴하므로, 총 256개의 값을 저장할 타입이 필요하다.
따라서,byte
타입을 리턴 타입으로 설정할 수 없다.'그러면
char
타입으로 하면 되지 않나?'라는 의문이 들 수 있지만, 정수형 중에서 연산이 가장 효율적이고 빠른int
타입을 사용한다.
이제 바이트 기반 스트림의 각 종류를 살펴본다.
메모리 즉, 바이트 배열에 데이터를 입출력 하는데 사용되는 스트림이다.
주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.
그리고 바이트 배열은 사용하는 자원이 메모리 밖에 없으므로 가비지컬렉터에 의해 자동으로 자원을 반환하므로, close()
로 스트림을 닫지 않아도 된다.
파일에 데이터를 입출력 하는데 사용되는 스트림이다.
실제 프로그래밍에서 많이 사용되는 스트림 중의 하나이다.
하지만 텍스트파일을 다루는 경우에는 FileReader
와 FileWriter
를 사용하는 것이 더 좋다.
다음은 FileInptuStream
과 FileOutputStream
의 생성자들이다.
생성자 | 설명 |
---|---|
FileInputStream(String name) | 지정된 파일명(name )을 가진 실제 파일과 연결된 FileInputStream 생성 |
FileInputStream(File file) | 지정된 파일 인스턴스(file )로 FileInputStream 생성 |
FileInputStream(FileDescriptor fdObj) | 지정된 파일 디스크립터(fdObj )로 FileInputStream 생성 |
FileOutputStream(String name) | 지정된 파일명(name )을 가진 실제 파일과 연결된 FileOutputStream 생성 |
FileOutputStream(String name, boolean append) | 지정된 파일명(name )을 가진 실제 파일과 연결된 FileOutputStream 생성- append 가 true 이면 기존 파일 내용의 마지막에 덧붙이고, false 이면 기존 파일 내용을 덮어쓴다. |
FileOutputStream(File file) | 지정된 파일 인스턴스(file )로 FileOutputStream 생성 |
FileOutputStream(File file, boolean append) | 지정된 파일 인스턴스(file )로 FileOutputStream 생성- append 가 true 이면 기존 파일 내용의 마지막에 덧붙이고, false 이면 기존 파일 내용을 덮어쓴다. |
FileOutputStream(FileDescriptor fdObj) | 지정된 파일 디스크립터(fdObj )로 FileOutputStream 생성 |
ByteArrayInputStream
과 ByteArrayOutputStream
은 자주 사용되지는 않지만 스트림을 이용한 입출력 방법을 보여 주는 예제를 작성하기에 적합해서, 이 스트림으로 여러 입출력 예제를 살펴보자.
예제. 바이트 배열 inSrc의 데이터를 다른 바이트 배열 outSrc로 옮기기
방법1. read()
로 한 바이트씩 옮기기
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
int data = 0;
while ((data = input.read()) != -1) {
output.write(data);
}
outSrc = output.toByteArray();
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("Output Source : " + Arrays.toString(outSrc));
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
방법2. read(byte[] b)
로 여러 byte씩 옮기기
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
byte[] temp = new byte[4];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
System.out.println("Input Source : " + Arrays.toString(inSrc));
try {
while(input.available() > 0) {
input.read(temp);
output.write(temp);
outSrc = output.toByteArray();
printArrays(temp, outSrc);
}
} catch (IOException e) {}
}
private static void printArrays(byte[] temp, byte[] outSrc) {
System.out.printf("%-15s: %s%n", "temp", Arrays.toString(temp));
System.out.printf("%-15s: %s%n", "Output Source", Arrays.toString(outSrc));
}
Input Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp : [0, 1, 2, 3]
Output Source : [0, 1, 2, 3]
temp : [4, 5, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7]
temp : [8, 9, 6, 7]
Output Source : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 7]
read(byte[] b)
는 보다 나은 성능을 위해 바이트 배열 b에 담긴 내용을 지우고 쓰는 것이 아니라 기존의 b위에 덮어 쓰기 때문이다.while(input.availavle() > 0) {
int len = input.read(temp);
output.write(temp, 0, len);
}
C언어와 달리 Java에서는 한 문자를 의미하는 char
형이 1byte가 아니라 2byte이기 때문에, 바이트 기반 스트림으로 2byte인 문자를 입출력하는 데는 어려움이 있다.
이 점을 보완하기 위해 문자 기반 스트림이 제공된다.
문자데이터를 입출력할 때는 바이트 기반 스트림 대신 문자 기반 스트림을 사용하자.
InputStream
👉Reader
OutputStream
👉Writer
모든 문자 기반 입력 스트림의 조상은 Reader
이고,
모든 문자 기반 출력 스트림의 조상은 Writer
이다.
문자 기반 입출력 스트림은 입출력 대상에 따라 다음과 같이 종류가 나뉜다.
입출력 대상 | 입력 스트림 | 출력 스트림 |
---|---|---|
파일 | FileReader | FileWriter |
메모리(char 배열) | CharArrayReader | CharArrayWirter |
프로세스(프로세스간 통신) | PipedReader | PipedWriter |
문자열 버퍼 | StringBufferReader | StringBufferWriter |
Reader
과 Writer
의 입출력 메소드
Reader
의 입력 메소드
Reader | 정의 |
---|---|
int read() | 하나의 char(2byte)를 읽어서 반환한다. - 더이상 읽어올 데이터가 없으면 -1을 반환한다. - 내부적으로 read(char[] b, int off, int len) 를 사용한다. |
int read(char[] cbuf) | 최대 char 배열 cbuf의 크기 만큼 데이터를 읽어서 cbuf에 저장한다. - 읽어 온 데이터의 수 또는 -1을 반환한다. - 내부적으로 read(char[] b, int off, int len) 를 사용한다. |
abstract int read(char[] cbuf, int off, int len) | 최대 len개의 char를 읽어서 char 배열 cbuf의 off 위치에 저장한다. - 읽어 온 데이터의 수 또는 -1을 반환한다. |
Writer
의 출력 메소드
Writer | 정의 |
---|---|
void write(int c) | 한 char(2byte)를 출력한다. - 내부적으로 write(char[] cbuf, int off, int len) 를 사용한다. |
void write(char[] cbuf) | 주어진 문자 배열 cbuf에 저장된 모든 문자를 작성한다. - 내부적으로 write(char[] cbuf, int off, int len) 를 사용한다. |
abstract void write(char[] cbuf, int off, int len) | 주어진 문자 배열 cbuf의 off위치부터 len개의 문자를 출력한다. |
void write(String str) | 주어진 문자열을 출력한다. |
void write(String str, int off, int len) | 주어진 문자열의 일부(off번째 문자부터 len개 만큼의 문자들)를 출력한다. |
Reader
과 Writer
는 여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해준다.
Reader
는 특정 인코딩을 읽어서 유니코드로 변환하고,
Writer
는 유니코드를 특정 인코딩을 변환하여 출력한다.
이제 문자 기반 스트림의 각 종류를 살펴본다.
FileInputStream
/ FileOutputStream
와 내용이 비슷하므로 생략한다.
쓰레드 간에 데이터를 주고받을 때 사용한다.
CharArrayReader
와 CharArrayWriter
와 같이 입출력 대상이 메모리인 스트림이다.
StringWriter
에 출력되는 데이터는 내부의 StringBuffer
에 저장되며, 다음의 두 메서드를 통해 저장된 데이터를 얻을 수 있다.
StringBuffer getBuffer()
: StringBuffer
반환String toString()
: StringBuffer
에 저장된 문자열 반환String inputData = "HELLO";
StringReader input = new StringReader(inputData);
StringWriter output = new StringWriter();
int data = 0;
while((data = input.read()) != -1) {
output.write(data);
}
System.out.println("Input Data : " + inputData);
System.out.println("Output Data : " + output.toString());
System.out.println("Output Data : " + output.getBuffer().toString());
Input Data : HELLO
Output Data : HELLO
Output Data : HELLO
데이터를 입출력하는 스트림(이를 기반 스트림이라 칭한다.)에 보조 기능(입출력 성능 향상 등)을 추가한 것이다.
보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에, 입출력시 기반스트림을 활용한다.
보조 스트림도 마찬가지로 바이트 기반 스트림의 보조 스트림, 문자 기반 스트림의 보조 스트림으로 나뉜다.
먼저, 바이트 기반 스트림의 보조 스트림을 살펴보자.
FilterInputStream
과 FilterOutputStream
public class FilterInputStream extends InputStream [
protected volatile InputStream in;
// 접근제어자가 protected이므로 FilterInputStream을 생성하여 사용할 수 없고 상속을 통해 오버라이딩되어야 한다.
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException { // 자식 클래스들은 해당 메소드를 오버라이딩하여 원하는 작업을 수행하도록 읽고 쓰게 한다.
return in.read();
}
}
FilterInputStream
과 FilterOutputStream
을 상속받은 바이트 기반 스트림의 보조 스트림의 종류는 다음과 같다.
FilterInputStream
의 자손 : BufferedInputStream
, DataInputStream
, PushbackInputStream
등FilterOutputStream
의 자손 : BufferedOutputStream
,DataOutputStream
, PrintStream
등이제 바이트 기반 스트림의 보조 스트림을 하나씩 살펴보자.
BufferedInputStream
과 BufferedOutputStream
스트림의 입출력 효율을 높이기 위해
데이터를 한 바이트 또는 문자씩 바로 보내는 것이 아니라,
버퍼(바이트 배열)에 담아놓았다가 한번에 모아서 보내는 방식이다.
생성자
생성자 | 설명 |
---|---|
BufferedInputStream(InputStream in, int size) | 주어진 InputStream 인스턴스를 입력소스로 하며, 지정된 크기(byte 단위)의 버퍼를 갖는 BufferedInputStream 인스턴스 생성 |
BufferedInputStream(InputStream in) | 주어진 InputStream 인스턴스를 입력소스로 하며, 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192byte 크기의 버퍼를 갖는 BufferedInputStream 인스턴스 생성 |
BufferedOutputStream(OutputStream out, int size) | 주어진 OutputStream 인스턴스를 출력 소스로 하며, 지정된 크기(byte 단위)의 버퍼를 갖는 BuffferedOutputStream 인스턴스를 생성한다. |
BufferedOutputStream(OutputStream out) | 주어진 OutputStream 인스턴스를 출력 소스로 하며, 버퍼의 크기를 지정해주지 않으므로 기본적으로 8192byte 크기의 버퍼를 갖는 BuffferedOutputStream 인스턴스를 생성한다. |
메소드
메소드 | 설명 |
---|---|
flush() | BufferedInputStream - 입력 소스로부터 버퍼 크기만큼 버퍼에 입력받는다.BufferedOutputStream - 버퍼의 모든 내용을 출력 소스에 출력한다. |
close() | flush() 를 호출하고, BufferedInputStream 이나 BufferedOutputStream 이 사용하던 모든 자원을 반환한다. |
데이터를 입력받는 과정은 다음과 같다.
1. 프로그램에서 입력소스로부터 데이터를 읽기 위해 처음으로 read()
를 호출한다.
2. BufferedInputStream
은 입력소스로부터 버퍼 크기만큼의 데이터를 읽어다 자신의 내부 버퍼에 저장한다.
3. 이제 프로그램에서는 read()
호출시마다 BufferedInputStream
버퍼에 저장된 데이터를 읽는다.
4. 버퍼에 저장된 모든 데이터를 다 읽으면, 2번으로 다시 돌아간다.
외부의 입력소스로부터 읽는 것보다 내부의 버퍼로 읽는 것이 훨씬 빠르고, OS 레벨인 시스템 콜의 횟수를 감소시켜 성능상 이점이 있다.
데이터를 출력하는 과정은 다음과 같다.
1. 프로그램에서 출력소스로에 데이터를 출력하기위해 처음으로 write()
를 호출한다.
2. BufferedOutputStream
은 출력할 데이터를 자신의 내부 버퍼에 저장한다.
3. write()
계속 호출하다가 버퍼가 가득 차면, 그 때 버퍼의 모든 데이터를 출력소스로 출력하고 2번으로 돌아간다.
버퍼가 가득 찼을 때만 출력소스에 출력을 하기 때문에, 모든 출력작업을 마친 후에 마지막 출력부분이 출력소스에 출력되지 못하고 BufferedOutputStream
의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다.
따라서, 마지막에는 반드시 flush()
나 close()
를 호출하여 버퍼에 있는 모든 내용이 출력소스로 출력되도록 하자.
예제 - BufferedOutputStream
// 1. 먼저 기반스트림을 생성한다.
FileOutputStream fos = new FileOutputStream("123.txt");
// 2. 기반스트림을 이용해서 보조스트림을 생성한다.
// 버퍼 크기를 5로 설정해 주었다.
BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
// 3. 보조스트림으로부터 데이터를 출력한다.
// 버퍼가 꽉 찰 때마다 123.txt에 출력된다.
for (char i = '1'; i <= '9'; i++) {
bos.write(i);
}
// 4. 보조스트림을 종료하여, 버퍼에 있는 모든 데이터를 출력한다.
// 보조스트림이 종료될 때, 기반 스트림도 같이 종료된다.
bos.close();
12345
123456789
DataInputStream
은 DataInput
인터페이스를 DataOutputStream
은 DataOutput
인터페이스를 구현했기 때문에, 데이터 입출력을 byte 단위가 아닌 8가지 기본 자료형의 단위로 읽고 쓸 수 있다.
DataOutputStream
이 출력하는 형식은 각 기본 자료형 값을 16진수로 표현하여 저장한다. 즉, 이진 데이터로 저장한다.
DataInputStream
과 DataOutputStream
의 메서드와 생성자는 다음과 같다.
메소드 / 생성자 | 설명 |
---|---|
DataInputStream(InputStream in) | 주어진 InputStream 인스턴스를 기반스트림으로 하는 DataInputStream 인스턴스를 생성한다. |
boolean readBoolean() byte readByte() char readChar() short readShort() int readInt() long readLong() float readFloat() double readDouble() int readUnsignedByte() int readUnsignedShort() | 각 타입에 맞게 값을 읽어온다. - 더 이상 읽을 값이 없으면 EOFException을 발생시킨다. |
void readFully(byte[] b) void readFully(byte[] b, int off, int len) | 입력스트림에서 지정된 배열의 크기만큼 또는 지정된 위치에서 len만큼 데이터를 읽어온다. - 파일의 끝에 도달하면 EOFException이 발생한다. - I/O 에러가 발생하면 IOException이 발생한다. |
String readUTF() | UTF-8 형식으로 쓰여진 문자를 읽는다. - 더 이상 읽을 값이 없으면 EOFException을 발생시킨다. |
static String readUTF(DataInput in) | 입력스트림(in)에서 UTF-8형식의 유니코드를 읽어온다. |
int skipBytes(int n) | 현재 읽고 있는 위치에서 지정된 숫자(n)만큼 건너뛴다. |
메소드 / 생성자 | 설명 |
---|---|
DataOutputStream(OutpuStream out) | 주어진 OutpuStream 인스턴스를 기반스트림으로 하는 DataOutputStream 인스턴스를 생성한다. |
void writeBoolean(boolean b) void writeByte(int b) void writeChar(int c) void writeShort(int s) void writeInt(int i) void writeLong(long i) void writeFloat(float f) void writeDouble(double d) | 각 타입에 맞게 값을 출력한다. |
void writeChars(String s) | 주어진 문자열을 출력한다. writeChar(int c)메서드를 여러번 호출한 결과와 같다. |
void writeUTF(String s) | UTF 형식으로 문자를 출력한다. |
int size() | 지금까지 DataOutputStream에 쓰여진 byte의 수를 알려준다. |
각 자료형의 크기가 다르므로, 출력한 데이터를 다시 읽어올 때는 출력했을 때의 순서를 염두에 두어야 한다.
FileOutputStream fos = new FileOutputStream("sample.dat");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeInt(10);
dos.writeFloat(21.2f);
dos.writeBoolean(true);
dos.close();
FileInputStream fis = new FileInputStream("sample.dat");
DataInputStream dis = new DataInputStream(fis);
System.out.println(dis.readInt());
System.out.println(dis.readFloat());
System.out.println(dis.readBoolean());
dis.close();
10
21.2
true
여러 개의 입력스트림을 연속적으로 연결해서 하나으 ㅣ스트림으로부터 데이터를 읽는 것과 같이 처리한다.
큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업을 수행할 때 사용하면 좋다.
SequenceInputStream
를 생성하는 두가지 방법SequenceInputStream(InputStream s1, InputStream s2)
: 두 개의 입력스트림을 하나로 연결한다.SequecneInputStream(Enumeration e)
: Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결한다.Vector
에 연결할 입력스트림들을 저장한 다음 Vector
의 Enumeration elements()
를 호출하여 생성자의 매개변수로 사용한다.데이터를 기반스트림에 문자로 출력할 수 있는 print
, println
, printf
와 같은 메소드를 데이터의 자료형에 맞게 오버로딩하여 제공한다.
데이터를 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다. 그래서 PrintSteram
보다 향상된 기능의 문자기반 스트림인 PrintWriter
가 추가되었으나 그 동안 매우 빈번히 사용되던 System.out
이 PrintStream
이다 보니 둘 다 사용할 수 밖에 없게 되었다.
void print
: 출력소스에 인자로 주어진 값을 문자로 출력한다.println
: 출력소스에 인자로 주어진 값을 문자로 출력하고, 마지막에 줄바꿈 문자를 출력한다.printf(String format, Object... args)
: 출력소스에 인자로 주어진 값을 주어진 형태의 문자로 출력한다.문자 기반 스트림의 모든 보조 스트림 조상은 Reader
와 Writer
이다.
이제 문자 기반 스트림의 보조 스트림을 하나씩 살펴보자.
BufferedInputStream
/ BufferedOutputStream
와 비슷한 내용은 생략한다.
BufferedReader
의 String readLine()
을 통해 데이터를 라인단위로 읽을 수 있다.BufferedWriter
의 newLine()
을 통해 줄바꿈을 수행할 수 있다.바이트 기반 스트림을 문자기반 스트림으로 연결시켜준다.
바이트 기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업을 수행한다.
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
System.out.print("문장을 입력하세요. : ");
line = br.readLine();
System.out.println("입력된 문장: " + line);
br.close();
문장을 입력하세요: hello
hello
콘솔로부터의 데이터 입력과 콘솔로의 데이터 출력을 의미한다.
자바에서는 표준 입출력을 위해 다음의 3가지 입출력 스트림을 제공한다.
System.in
- 콘솔로부터 데이터를 입력받는데 사용한다.System.out
- 콘솔로 데이터를 출력하는데 사용한다.System.err
- 콘솔로 데이터를 출력하는데 사용한다.public final class System {
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();
}
이 3가지는 System
클래스에 선언된 static 변수이며, 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성된다.
또한, 버퍼를 이용하는 BufferedInputStream
과 BufferedOutputStream
의 인스턴스를 사용하기 때문에, 콘솔 입력시 Enter키나 입력의 끝(EOF)을 알리는 ^z
(윈도우 기준)를 입력할 때까지(blocking 상태가 풀릴 때까지) 편집이 가능하며 한 번에 버퍼의 크기만큼 입력이 가능하다.
콘솔로부터 데이터를 입력받는데 사용한다.
^z
(윈도우 기준)을 입력하면 데이터 입력이 종료되고 read()
는 -1을 리턴한다.\r
(carrage return, 커서를 현재 라인의 첫번째 컬럼으로 이동시킨다.), \n
(new line, 커서를 다음 줄로 이동시킨다.)가 입력된다.\r
와 \n
를 매번 제거해줘야는 불편함이 있다.System.in
에 BufferedReader
를 이용해서 BufferedReader
의 readLine()
을 통해 라인단위로 데이터를 입력받으면, 반환 값에서 \r
와 \n
가 제거되어 위와 같은 불편함이 사라진다.Scanner
와 Console
같은 클래스가 추가되면서 많이 보완되었다.콘솔로 데이터를 출력하는데 사용한다.
콘솔로 데이터를 출력하는데 사용한다.
System.in
, System.out
, System.err
의 입출력 대상은 기본적으로 콘솔 화면이지만 변경이 가능하다.
변경하는 메소드는 다음과 같다.
메소드 | 설명 |
---|---|
static void setIn(InputStream in) | System.in 의 입력을 지정된 InputStream 으로 변경 |
static void setOut(PrintStream out) | System.out 의 출력을 지정된 PrintStream 으로 변경 |
static void setErr(PrintStream out) | System.err 의 출력을 지정된 PrintStream 으로 변경 |
class StandardIOExample {
public static void main(String[] args) {
PrintStream ps = null;
FileOutputStream fos = null;
fos = new FileOutputStream("test.txt");
ps = new PrintStream(fos);
System.setOut(ps);
System.out.println("hello by system.out");
System.err.println("hello by system.err");
}
}
C:\...> java StandardIOExample
hello by system.err
C:\...> type test.txt
hello by system.out
자바4부터 자바의 기본 입출력 방식이었던 Stream의 문제점을 해결하고자 NIO(New Input Output)가 java.nio
패키지에 포함되어 등장하였고, Channel이 NIO의 기본 입출력 방식이다.
이제부터 NIO가 기존 방식과 달라진 부분을 살펴보자.
출처 : 이것이 자바다
IO | NIO | |
---|---|---|
입출력 방식 | 스트림 | 채널 |
버퍼 유무 | non-buffer | buffer |
비동기 지원 유무 | 지원 안함 | 지원 |
블로킹 / 넌블로킹 방식 | 블로킹 방식만 지원(동기) | 블로킹 / 넌블로킹 방식 모두 지원 (동기 / 비동기 모두 지원) |
스트림은 입력 스트림과 출력 스트림으로 구분되어 있어
데이터를 읽기 위해서는 입력 스트림을 출력하기 위해서는 출력 스트림을 생성해야한다.
데이터가 흘러다니는 양방향의 통로이다.
양방향이기 때문에 입력과 출력을 구분하지 않아 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.
데이터를 버퍼가 없이 바로 입출력한다.
스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면, 입력된 데이터의 위치를 이동해가면서 자유롭게 이용할 수 없다.
따라서 버퍼를 제공해주는 보조 스트림인 BufferedInputStream
, BufferedOutputStream
을 연결해 사용하기도 한다.
기본적으로 버퍼를 통해서만 입출력이 가능하다.
버퍼 내에서 데이터의 위치를 이동해가며 필요한 부분만 읽고 쓸 수 있다.
입력 스트림의 read()
를 호출하면 데이터가 입력될 때까지 Thread는 블로킹된다.
출력 스트림의 write()
를 호출하면 데이터가 출력될 때까지 Thread 는 블로킹된다.
IO Thread가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 interrupt도 할 수 없다.
블로킹을 빠져나오는 유일한 방법은 스트림을 닫는 것이다.
Thread를 Interrupt함으로써 빠져나올 수 있다.
넌블로킹은 과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용할 수 있다.
🤔 그렇다면 무조건
IO
보다NIO
가 좋은 것일까?
아니다.
첫째, 입출력 처리가 오래걸리는 작업일 경우 스레드를 재사용하여 Non-blocking 방식으로 처리하는NIO
는 좋은 효율을 내지 못할 수 있다.
둘째, 대용량 데이터를 처리해야할 경우,NIO
의 버퍼 할당 크기가 문제가 된다.
셋째, 모든 입출력 작업에 버퍼를 무조건 사용해야 하므로 즉시 처리하는IO
보다 복잡하다.정리하자면,
NIO
는 불특정 다수의 클라이언트를 연결하거나 하나의 입출력 처리작업이 오래걸리지 않는 경우에 사용하고,
IO
는 연결 클라이언트 수가 적고 전송되는 데이터가 대용량이면서 순차적으로 처리될 필요성이 있는 경우에 사용한다.
직렬화는 객체를 컴퓨터에 저장했다가 다음에 다시 꺼내쓰거나 네트워크를 통해 컴퓨터 간에 객체를 주고받을 수 있도록 한다.
즉, 직렬화란 객체를 데이터 스트림으로 만드는 것으로 객체에 저장된 데이터를 스트림에 쓰기위해 연속적인 데이터(바이트)로 변환하는 것을 말한다.
반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라고 한다.
객체를 직렬화하기 위해서는 변수 타입이 기본형 타입이거나 java.io.Serializable
인터페이스를 상속 받아야 한다.
Reference
- 자바의 정석 3rd Edition, 남궁성 지음
- I/O