입출력이란?
I/O란 Input과 Output의 약자로 입력과 출력, 간단히 입출력이라고 한다. 입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.
예) 키보드로부터 데이터를 입력받음, System.out.println()으로 화면에 출력함
스트림
자바에서 입출력을 하기 위해 한쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림이라고 한다.
14장의 스트림과 같은 용어를 쓰지만 다른 개념이다.
스트림 - 데이터를 운반하는데 사용되는 연결통로
스트림은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름이다.
물이 한쪽으로만 흐르는 것과 같이 스트림은 단방향통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.
그래서 입력과 출력을 동시에 수행하려면 입력을 위한 입력 스트림과 출력을 위한 출력스트림, 모두 2개의 스트림이 필요하다.
스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고받는다.
큐와 같은 FIFO 구조로 되어있다고 이해하면 쉽다.
바이트기반 스트림
| 입력스트림 | 출력스트림 | 입출력 대상의 종류 |
| FileInputStream | FileOutputStream | 파일 |
| ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte 배열) |
| pipedInputStream | pipedOutputStream | 프로세스(프로세스간의 통신) |
| AudioInputStream | AudioOutputStream | 오디오 장치 |
보조 스트림
스트림의 기능을 보완하기 위한 보조스트림이 있다. 보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없지만, 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.
그래서 스트림을 먼저 생성한 다음에 이를 이용해서 보조스트림을 생성해야한다.
예를 들어 test.txt라는 파일을 읽기위해 FileInputStream을 사용할 때, 입력 성능을 향상시키기 위해 버퍼를 사용하는 보조스트림인 BufferedInputStream을 사용하는 코드는 다음과 같다.
// 먼저 기반스트림을 생성
FileInputStream fis = new FileInputSteam("test.txt");
// 기반스트림을 이용해서 보조스트림을 생성
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read(); // 보조스트림인 BufferedInputStream으로부터 데이터를 읽는다.
보조스트림의 종류
| 입력 | 출력 | 설명 |
| FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
| BufferedInputStream | BufferedOutputStream | 버퍼를 이용한 입출력 성능향상 |
| DataInputStream | DataOutputStream | int, float와 같은 기본형 단위(primitive type)로 데이터를 처리하는 기능 |
| SequenceInputStream | 없음 | 두 개의 스트림을 하나로 연결 |
| LineNumberInputStream | 없음 | 읽어 온 데이터의 라인 번호를 카운트 (JDK1.1부터 LineNumberReader로 대체) |
| ObjectInputStream | ObjectOutputStream | 데이터를 객체단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화와 관련있음 |
| 없음 | PrintStream | 버퍼를 이용하며, 추가적인 print 관련 기능 (print, printf, println 메서드) |
| PushbackInputStream | 없음 | 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능(unread, push back to buffer) |
문자기반 스트림 - Reader, Writer
지금까지 알아본 스트림은 모두 바이트기반의 스트림이다. 바이트기반이라 함은 입출력의 단위가 1 byte라는 뜻이다.
하지만 자바에서는 한 문자를 의미하는 char 형이 1 byte가 아니라 2 byte이기 때문에 바이트기반 스트림으로는 어려움이 있다.
이 점을 보완하기 위해서 문자기반의 스트림이 제공된다. 문자데이터를 입출력 할때는 바이트기반 스트림 대신 문자기반 스트림을 사용하자.
InputStream -> Reader
예) FileInputStream -> FileReader
OutputStream -> Writer
예) FileWriter
FileInputStream과 FileOutputStream
파일에 입출력을 하기 위한 스트림. 실제로 많이 사용되는 스트림 중 하나이다.
import java.io.*;
class FileViewer {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(args[0]);
int data = 0;
while ((data=fis.read()) != -1) {
char c = (char)data;
System.out.print(c);
}
}
}
텍스트파일을 다루는 경우에는 FileInputStream/FileOutputStream보다 문자기반의 스트림인 FileReader/FileWriter를 사용하는 것이 좋다.
바이트기반의 보조스트림
FilterInputStream과 FilterOutputStream
FilterInputStream과 FilterOutputStream는 InputStream/OutputStream의 자손이면서 모든 보조스트림의 조상이다.
보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반 스트림이 필요하다.
다음은 FilterInputStream과 FilterOutputStream의 생성자다.
protected FilterInputStream(InputStream in)
public FilterOutputStream(OutputStream out)
BufferedInputStream과 BufferedOutputStream
스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조스트림이다. 한 바이트씩 입출력하는 것 보다는 버퍼(바이트배열)를 이용해서 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문에 대부분의 입출력 작업에 사용된다.
| BufferedInputStream의 생성자 | BufferedOutputStream의 생성자 |
| BufferedInputStream(InputStream in, int size) BufferedInputStream(InputStream in) |
BufferedOutputStream(OutputStream out, int size) BufferedOutputStream(OutputStream out) flush() close() |
DataInputStream과 DataOutputStream
각각 FilterInputStream/FilterOutputStream의 자손이며 DataInputStream은 DataInput인터페이스를, DataOutputStream은 DataOutput인터페이스를 각각 구현하였기 때문에, 데이터를 읽고 쓰는데 있어서 byte 단위가 아닌, 8가지 기본 자료형의 단위로 읽고 쓸 수 있다는 장점이 있다.
SequenceInputStream
여러 개의 입력스트림을 연속적으로 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 도와준다. SequenceInputStream의 생성자를 제외하고 나머지 작업은 다른 입력스트림과 다르지 않다.
큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업을 수행할 때 사용하면 좋다.
| 메서드/생성자 | 설명 |
| SequenceInputStream(Enumeration e) | Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결한다 |
| SequenceInputStream(InputStream s1, InputStream s2) | 두 개의 입력스트림을 하나로 연결한다 |
PrintStream
데이터를 기반스트림에 다양한 형태로 출력할 수 있는 print, println, printf 와 같은 메서드를 오버로딩하여 제공한다. 데이터를 적절한 문자로 출력하는 것이기 때문에 문자기반 스트림의 역할을 수행한다.
하지만 JDK1.1에서부터 향상된 기능의 문자기반 스트림인 PrintWriter가 추가되었기때문에 가능하면 PrintWriter를 사용하는 것이 좋다.
문자기반 스트림
문자 데이터를 다루는데 사용된 가는 것을 제외하고는 바이트기반 스트림과 문자기반 스트림의 사용방법은 거의 같다.
Reader와 Writer
바이트기반 스트림의 조상이 InputStream/OutputStream인 것과 같이 문자기반의 스트림에서는 Reader/Writer가 그와 같은 역할을 한다.
Reader/Writer의 메서드는 byte배열 대신 char배열을 사용한다는 것 외에는 InputStream/OutputStream의 메서드와 다르지 않다.
FileReader와 FileWriter
파일로부터 텍스트데이터를 읽고, 파일에 쓰는데 사용된다.
import java.io.*;
claass FileReaderEx1 {
public static void main(String[] args) {
try {
String fileName = "test.txt";
FileInputStream fis = new FileInputStream(fileName);
FileReader fr = new FileReader(fileName);
int data = 0;
// FileInputStream을 이용해서 파일내용을 읽어 화면에 출력한다.
while((data=fis.read()) != -1) {
System.out.println((char)data);
}
System.out.println();
fis.close();
// FileReader를 이용해서 파일내용을 읽어 화면에 출력한다.
while((data=fr.read()) != -1)
System.out.println((char)data);
System.out.println();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
위에 결과에서 알 수 있듯이 FileInputStream을 사용했을 땐 한글이 깨져서 출력되고 FileReader를 사용했을 땐 출력이 잘 된다.
PipedReader와 PipedWriter
쓰레드 간에 데이터를 주고받을 때 사용된다. 다른 스트림과는 달리 입력과 출력스트림을 하나의 스트림으로 연결해서 데이터를 주고받는다는 특징이 있다.
스트림을 생성한 다음에 어느 한쪽 쓰레드에서 connect()를 호출해서 입력스트림과 출력스트림을 연결한다. 입출력을 마친 후에는 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힌다.
StringReader와 StringWriter
입출력 대상이 메모리인 스트림이다. StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며 StringWriter의 다음과 같은 메서드를 이용해서 저장된 데이터를 얻을 수 있다.
StringBuffer getBuffer() - StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환한다
String toString() - StringWriter에 출력된 (StringBuffer에 저장된) 문자열을 반환한다
문자기반의 보조스트림
BufferedReader와 BufferedWriter
버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 한다. 버퍼를 이용하면 입출력의 효율이 비교할 수 없을 정도로 좋아지기 때문에 사용하는 것이 좋다.
BufferedReader의 readLine()을 사용하면 데이터를 라인단위로 읽을 수 있고 BufferedWriter는 newLine()이라는 줄바꿈 해주는 메서드를 가지고 있다.
InputStreamReader와 OutputStreamWriter
바이트기반 스트림을 문자기반 스트림으로 연결시켜주는 역할을 한다. 그리고 바이트기반 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업을 수행한다.
한글 윈도우에서 중국어로 작성된 파일을 읽을 때 InputStreamReader(InputStream in, String encoding)를 이용해서 인코딩이 중국어로 되어 있다는 것을 지정해주어야 파일의 내용이 깨지지 않고 올바르게 보일 것이다. 해주지 않는다면 OS에서 사용하는 인코딩을 사용해서 파일을 해석해서 보여주기 때문에 원래 작성된 데로 볼 수 없을 것이다.
표준입출력과 File
표준입출력 - System.in, System.out, System.err
표준입출력은 콘솔(console, 도스창)을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다. System.in, System.out, System.err 이들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다.
표준입출력의 대상변경 - setOut(), setErr(), setln()
초기에는 System.in, System.out, System.err의 입출력대상이 콘솔화면이지만, setOut(), setErr(), setln()를 사용하면 입출력을 콘솔 이외에 다른 입출력 대상으로 변경하는 것이 가능하다.
그러나 JDK1.5부터 Scanner 클래스가 제공되면서 데이터를 입력받아 작업하는 것이 편리해졌다.
File
파일은 기본적이면서도 가장 많이 사용되는 입출력 대상이기 때문에 중요하다. 자바에서는 File 클래스를 통해서 파일과 디렉토리를 다룰수 있도록 하고있다. 그래서 File 인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다.
직렬화
객체를 데이터 스트림으로 만드는 것을 뜻한다. 객체에 저장된 데이터를 스트림에 쓰기(write) 위해 연속적인 데이터로 변환하는 것을 말한다.
반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화하고 한다.
ObjectInputStream, ObjectOutputStream
직렬화에는 ObejctOutputStream을 사용하고 역직렬화에는 ObejctInputStream을 사용한다.
이 둘은 기반 스트림을 필요로 하는 보조 스트림이다. 그래서 객체를 생성할 때 입출력할 스트림을 지정해 주어야한다.
ObjectInputStream(InputStream in)
ObejctOutputStream(OutputStream out)
만일 파일에 객체를 저장(직렬화)하고 싶다면 다음과 같이 하면 된다.
직렬화가 가능한 클래스 만들기 - Serializable, transient
직렬화가 가능한 클래스를 만드는 방법은 직렬화하고자 하는 클래스가 java.io.Serializable 인터페이스를 구현하도록 하면 된다.
예를 들어 UserInfo라는 클래스가 있을 때, 이 클래스가 직렬화가 가능하도록 변경하려면 이와같이 변경하면 된다.
public class UserInfo {
String name;
String password;
int age;
}
----------Serializable----------
public class UserInfo implements java.io.Serializable {
String name;
String password;
int age;
}
transient를 붙이면 직렬화 대상에서 제외할 수 있다.
public class UserInfo implements Serializable {
String name;
transient String password; // 직렬화 대상에서 제외
int age;
transient Object obj = new Object(); // 직렬화 대상에서 제외
}'자바 > Java의 정석' 카테고리의 다른 글
| [Chapter 16] 네트워킹 (0) | 2024.12.05 |
|---|---|
| [Chapter 14] 람다와 스트림 (1) | 2024.11.30 |
| [Chapter 13] 쓰레드 (2) | 2024.11.30 |
| [Chapter 12] 지네릭스, 열거형, 애너테이션 (1) | 2024.11.26 |
| [Chapter 11] 컬렉션 프레임웍 (1) | 2024.11.26 |