13주차 과제: I/O
목표
자바의 Input과 Ontput에 대해 학습하세요.
학습할 것
- 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
- InputStream과 OutputStream
- Byte와 Character 스트림
- 표준 스트림 (System.in, System.out, System.err)
- 파일 읽고 쓰기
스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
I/O란 Input과 Output의 약자로 입력과 출력, 줄여서 입출력이라고 말합니다. 입출력이란 컴퓨터 내부 또는 외부의 장치와 프로그램 간의 데이터를 주고받는 것을 말합니다.
스트림
자바에서 모든 입출력은 스트림(Stream)을 통해 이루어집니다.
스트림은 데이터를 운반하는 연결통로 입니다. 스트림은 자료의 흐름이 물의 흐름과 같다는 의미에서 해석됩니다.
스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 합니다.
물이 한쪽 방향으로만 흐르는 것과 같이 스트림도 한쪽 방향으로 흐르는데 이를 단방향 통신이라고 하며, 이러면 입력과 출력을 동시에 처리할 수 없습니다.
따라서 입출력 동시에 가능하게 하려면 입력 스트림과 출력 스트림 모두 필요합니다.
자바에서는 java.io
패키지를 통해 InputStream과 OutputStream 클래스를 별도로 제공하고 있습니다. 자바에서의 스트림 생성이란 이러한 스트림 클래스 타입의 인스턴스를 생성한다는 의미입니다.
버퍼
버퍼(Buffer)란 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 데이터를 보관하는 임시 메모리 영역을 말합니다.
일반적으로 버퍼를 사용하지 않으면 데이터를 입력하면 바로 전달되어 출력됩니다.
하지만 버퍼를 사용하게 되면 데이터를 입력하면 임시 메모리 영역에 일시적으로 데이터를 모아둡니다. 그리고 이 메모리 영역이 모두 꽉 차거나 개행 문자가 나타나면 버퍼의 데이터를 모두 한꺼번에 전송합니다. 그리고나서 출력됩니다.
그렇다면 왜 버퍼를 사용할까요?
하드뿐만 아니라 키보드나 모니터와 같은 외부 장치와의 데이터 입출력은 생각보다 시간이 걸리는 작업입니다. 버퍼없이 키보드가 눌릴 때마다 계속 눌린 데이터의 정보를 바로 이동시키는 것은 비효율적입니다.
차라리 버퍼라는 임시 공간에 데이터를 일시적으로 모아두었다가 한꺼번에 전송하는 것이 더 효율적일 것입니다.
예를 들어, 물류센터 같은 곳에서 물건을 운반해야 할 때 하나씩 물건을 옮기나요 아니면 수레 같은 곳에 모아두었다가 한꺼번에 옮기나요?
버퍼에 있는 I/O 클래는 두 가지가 있습니다. BufferedReader / BufferedWriter
이 두 클래스는 입력된 데이터는 바로 전달되지 않고 버퍼에 모아두었다가 전송됩니다.
채널
채널을 이해하기 위해서는 자바 I/O의 Blocking
과 자바 NIO의 Non-Blocking
을 이해해야 합니다. 자바 I/O에서 쓰레드에는 Reader와 Writer만 존재합니다. 따라서 Reader로 블로킹을 하고 Writer로 블로킹을 풀어주면 됩니다.
그렇다면 자바 NIO는 Non-Blocking 방식인데 데이터를 주고 받기 위해서는 쓰레드와 데이터가 들어간 버퍼 사이에 일종의 터널을 만들어주어야 합니다. 이 터널 역할을 하는 것이 바로 채널(Channel)입니다.
채널의 특징은 다음과 같습니다.
- 소켓과 연결되어 입출력 역할을 수행합니다.
- 입력과 출력을 동시에 수행합니다.
- 블로킹된 쓰레드를 깨우거나 다시 블로킹할 수 있습니다.
InputStream과 OutputStream
InputStream은 바이트 기반의 입력 스트림의 최상위 클래스로 추상 클래스입니다. 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 구현됩니다. InputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드들이 정의되어 있습니다.
OutputStream은 바이트 기반의 출력 스트림의 최상위 클래스로 추상 클래스입니다. 모든 바이트 기반 출력 스트림은 이 클래스를 상속받아서 구현됩니다. OutputStream 클래스에는 바이트 기반 출력 스트림이 기본적으로 가져야 할 메소드들이 정의되어 있습니다.
read()
메소드는 해당 입력 스트림에서 더 이상 읽어들일 바이트가 없으면 -1을 반환해야 합니다.
그런데 반환 타입을 byte 타입으로 하면 0부터 255까지의 바이트 정보는 표현할 수 있지만 -1은 표현할 수 없게 됩니다.
따라서 InputStream의 read()
메소드는 반환 타입을 int형으로 선언하고 있습니다.
Byte와 Character 스트림
스트림은 바이트 단위로 작업을 수행합니다. 문자 같은 경우도 내부적으로 바이트 단위로 되어 있으며 프로그램에서 문자를 사용할 때는 문자 인코딩으로 변환해서 사용해야 합니다.
이러한 불편한 점을 해소하기 위해 스트림 차원에서 문자를 처리해주는 문자(Character) 스트림을 제공하고 있습니다.
즉, 바이트 단위로 처리해야 한다면 바이트(Byte) 스트림
문자 단위로 처리해야 한다면 문자(Character) 스트림을 사용하면 됩니다.
바이트 스트림
위에서 말했다시피 바이트 스트림은 데이터를 바이트 단위로 주고받는 것을 말합니다.
대표적인 바이트 스트림으로 InputStream
과 OutputStream
이 있으며 이 두 클래스를 사용한다는 것은 데이터가 모두 바이트로 처리된다는 점을 알 수 있습니다.
그림이나 텍스트, zip, jar 파일과 같은 압축 파일도 모두 바이트로 되어 있습니다.
문자 스트림
문자 스트림은 데이터를 문자 단위로 주고받는 것을 말합니다.
바이트들을 2바이트씩 묶어서 사용할 수도 있고 1바이트 단위로 사용할 수도 있습니다. 자바에서 사용하는 문자 방식은 유니코드 방식입니다.
그래서 바이트로 전송되어지는 것을 스트림에서 재해석한 후에 유니코드 문자로 변환하게 됩니다. 결과적으로 바이트를 문자로 가공하며 문자의 인코딩은 문자 스트림에서 자동으로 해석하게 됩니다.
표준 스트림 (System.in, System.out, System.err)
자바에서는 콘솔과 같은 표준 입출력 장치를 위해 System
이라는 표준 입출력 클래스를 정의하고 있습니다.
자바에서는 모든 것이 객체로 표현되므로 입출력을 담당하는 수단 또한 객체이빈다.java.lang
패키지에 포함되어 있는 System 클래스는 표준 입출력을 위해 다음과 같은 클래스 변수(static)를 제공합니다.
표준 입출력 스트림은 자바가 자동으로 생성하므로 개발자가 별도로 스트림을 생성하지 않아도 사용할 수 있습니다.
System.in
은 값을 입력받는 클래스로 보시면 됩니다. (Scanner도 System.in
를 사용하고 있죠.)
값은 여러가지 방식으로 들어오기 때문에 만약 잘못된 값이 들어온 경우에 예외 처리를 꼭 해주어야 합니다. 보통은 IOException
을 사용하여 예외처리를 합니다.
System.out
이나 System.err
은 출력하는 클래스로 보시면 됩니다.
흔히 System.out.println()
메소드를 사용하면 모니터에 전달된 데이터를 출력한 후에 줄 바꿈까지 해줍니다.
System.out.println()
이런식으로 바로 메소드를 사용할 수 있는 이유는 PrintStream
타입의 static 변수인 out
이 선언되어 바로 println()
메소드를 사용할 수 있습니다.
System.err
는 에러가 발생할 때 알려주어야 할 내용으로 출력됩니다. 이 역시 타입이 PrintStream
입니다.
파일 읽고 쓰기
파일 쓰기
package azurealstn;
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("c:out.txt");
for (int i = 1; i < 11; i++) {
String data = i + "번째 줄입니다.\r\n";
fw.write(data);
}
fw.close();
}
}
FileOutputStream
객체를 생성하면 바이트 단위로 데이터를 처리하기 때문에 String 타입을 byte 타입으로 변환해주어야 합니다.
하지만 FileWriter
객체를 생성하면 따로 변환해주지 않아도 문자열 그대로 사용할 수 있습니다.
만약 \r\n
과 같은 줄 바꿈을 생략하고 싶다면 PrintWriter
객체를 사용하면 됩니다.
파일 읽기
package azurealstn;
import java.io.FileInputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
byte[] b = new byte[1024];
FileInputStream fileInputStream = new FileInputStream("c:out.txt");
fileInputStream.read(b);
System.out.println(new String(b));
fileInputStream.close();
}
}
생성한 파일을 읽기 위해서는 FileInputStream
클래스를 이용하면 됩니다. 다만 바이트 단위로 읽어야 하기 때문에 바이트 배열을 선언할 때 크기를 설정해야하는 불편함이 있습니다.
바이트 단위로 읽는 것이 아닌 라인 단위로 읽을 수 있으면 훨씬 편할 것입니다.
package azurealstn;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("c:out.txt"));
while (true) {
String line = br.readLine();
if (line == null) break;
System.out.println(line);
}
br.close();
}
}
BufferedReader
클래스를 이용하면 라인 단위로 읽을 수 있습니다.BufferedReader
클래스의 readLine()
메소드는 더 이상 읽을 라인이 없을 경우에는 null을 리턴하기 때문에 null 체크를 해주는 것이 좋습니다.
References
'Java' 카테고리의 다른 글
[자바 라이브 스터디] 15. 람다식 (0) | 2021.10.26 |
---|---|
[자바 라이브 스터디] 14. 제네릭 (0) | 2021.10.23 |
[자바 라이브 스터디] 12. 어노테이션 (0) | 2021.10.19 |
[자바 라이브 스터디] 11. 열거형 (0) | 2021.10.18 |
[자바 라이브 스터디] 10. 쓰레드 (0) | 2021.10.13 |
댓글