I/O Stream이란 byte나 char의 흐름이다. 그리고 byte 단위의 입출력 클래스는 InputStream, OutputStream의 후손이다. 마찬가지로 char 단위의 입출력 클래스는 Reader, Writer의 후손이다. 그리고 이들은 모두 추상 클래스이다.
또한 자바 I/O 객체는 사용하면 꼭 close를 해줘야한다.
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/package-summary.html
InputStream
읽어야할 대상 --> InputStream --> int read(), int read(byte[])
이때, read()는 1byte씩, read(byte[])는 여러 byte를 한꺼번에 받아서 읽는다.
InputStream in = new FileInputStream("읽어야할 파일");
in.read();
in.read(byte[]);
위 개념을 적용하여 파일을 한줄씩 byte로 받아서 읽으면 다음과 같이 출력된다.
package theory.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class HelloIOExam02 {
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream("/tmp/hello.dat");
int in1 = in.read();
System.out.println("in1 = " + in1); // 1
int in2 = in.read();
System.out.println("in2 = " + in2); // 255
int in3 = in.read();
System.out.println("in3 = " + in3); // 0
int in4 = in.read();
System.out.println("in4 = " + in4); // EOF. -1
in.close();
}
}
그리고 위 코드를 EOF만나면 읽어들이는 것을 종료하고자 한다면 다음과 같이 코드를 작성할 수 있다.
int buf = -1;
while ((buf = in.read()) != -1) {
System.out.println(buf);
}
OutputStream
write(int), write(byte[]) --> OutputStream -- > 써야할 대상
int를 넣어주면 int의 마지막 1 byte만 써진다.
OutputStream out = new FileOutputStream("써야할 파일");
out.write(int값);
out,writer(byte배열,0,길);
즉, 다음과 같이 코드를 작성하고, 생성된 파일을 hex editor로 분석하면
package theory.io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class HelloIOExam {
public static void main(String[] args) throws IOException {
OutputStream out = new FileOutputStream("/tmp/hello.dat");
out.write(1); // 0000 0000 0000 0000 0000 0000 "0000 0001"만 저장
out.write(255);
out.write(0);
out.close();
}
}
다음과 같이 저장된 바이너리 파일을 분석할 수 있다.
Reader ( Character Input Stream)
Reader는 문자 단위로 읽어들인다.
1개의 문자는 2byte를 차지한다.
그래서 Reader의 read 메서드를 사용하게 되면, 정수값 중에서 맨 끝의 2바이트에 값을 채워서 값을 return해준다. 즉, 읽어야할 대상 파일이 10byte이면 5번만 read 해주면된다.
다음 코드를 보면 생성자에 읽어야할 대상이 들어오는 것을 볼 수 있다.
Reader in = new FileReader("/tmp/hello.txt");
int ch1 = in.read();
System.out.println("(char) ch1 = " + (char) ch1);
int ch2 = in.read();
System.out.println("(char) ch2 = " + (char) ch2);
int ch3 = in.read();
System.out.println("(char) ch3 = " + (char) ch3);
int ch4 = in.read();
System.out.println("ch4 = " + ch4);
in.close();
혹은 다음처럼 문자 단위로 읽을 수 있다.
int ch = -1;
while ((ch = in.read()) != -1) {
System.out.println("(char) ch = " + (char) ch);
}
Writer ( Character Output Stream )
Writer를 이용해서 영어와 한글을 저장할때, 영어는 아스키 문자로 1byte씩 저장되지만, 한글은 3byte씩 저장되는 것을 확인할 수 있었다.
Writer out = new FileWriter("/tmp/hello.txt");
out.write((int) 'a');
out.write((int) 'h');
out.write((int) '!');
out.close();
3 hello.txt
Writer out = new FileWriter("/tmp/hello.txt");
out.write((int) '가');
out.write((int) '나');
out.write((int) '다');
out.close();
9 hello.txt
이유는 자바는 Unicode를 사용하기 때문이다. 그리고 그 종류에 따라 또 다르다.
응용
IO Stream은 생성자에 들어온 것을 통해서 읽는다.
위 그림처럼 InputStreamReader를 호출하면, 생성자에 들어온 InputStream의 read 메서드를 호출한다. 그렇게 파일로부터 2바이트를 읽어서 문자로 변환되어서 2바이트가 나오는 것이다. 그리고 BufferedReader는 내부적으로 buffer를 가지고있다. 이때, readLine을 호출하면 생성자에 들어온 Reader의 read메서드를 호출한다. 그렇게 한줄이 채워질 때까지 read를 호출하고 한줄이 채워지면 문자열을 반환한다.
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/tmp/my.txt")));
String line1 = in.readLine(); // hello
String line2 = in.readLine(); // world
String line3 = in.readLine(); // 1
String line4 = in.readLine(); // null
System.out.println("line1 = " + line1);
System.out.println("line2 = " + line2);
System.out.println("line3 = " + line3);
System.out.println("line4 = " + line4);
in.close();
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/tmp/my.txt")));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
다음 코드를 보면
// 한줄씩 출력하는 메서드를 가진다.
PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("/tmp/my.txt")));
FileOutputStream의 write(int) 메서드는 int의 마지막 byte만 저장한다. 그리고 "/tmp/my.txt"에 저장한다.
그리고 OutputStreamWriter는 Writer이기 때문에 write(int) 를 지고, 이는 int의 끝부분 char를 저장한다.
이때, OutputStreamWriter는 생성자에 들어온 FileOutputStream의 write()를 이용하여 쓴다.
그리고 PrintWriter는 println(문자열) 메서드를 가지고, 이 메서드는 문자열을 출력한다. 또한 마찬가지로 PrintWriter는 생성자에 들어온 OutputStreamWriter의 write()를 이용하여 쓴다.
// 한줄씩 출력하는 메서드를 가진다.
PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("/tmp/my.txt")));
out.println("hello");
out.println("world");
out.println("1");
out.close();
이런식으로 Stream 객체들을 연결한다. 이렇게 조립하는 것은 레고 조립과도 비슷하다. 즉, 경험치가 높아야한다는 의미이다. 또한 IO 패키지에 있는 클래스들이 어떤 역할을 수행할 수 있는지 알아야 한다.
Composite Pattern
다음은 Composite Pattern이다.
다음 그림은 Foler는 파일 컴포넌트의 자식들을 가진다.
그리고 자바 IO는 Decorator pattern으로 만들어졌다. 그런데 위에 3가지 클래스 부분이 Composite Pattern과 닮았다. 즉, 데코레이터 패턴은 컴포짓 패턴을 응용한 것인데, 그 개념은 다르다.
Composite : 동일시화 하는 패턴이라면, Decorator Pattern : 동일시화 하지만 장식하는 것을 더 강조한 패턴이다.
인용
https://java8.info/inputoutput/javaioovw.html
https://youtu.be/26342ZhRukc?si=EfkZdQCgQVNz9Ofh
'Language > Java' 카테고리의 다른 글
I/O - 다양한 IO객체들, 객체 직렬화 (0) | 2023.09.18 |
---|---|
I/O - Decorator Pattern (0) | 2023.09.18 |
I/O - File 클래스 (0) | 2023.09.18 |
I/O (Input, Output) (0) | 2023.09.17 |
제네릭과 컬렉션 프레임워크 (0) | 2023.09.16 |