파일 입출력
git/eomcs-java-basic/src/main/java com.eomcs.io.ex03.Exam0420
git/eomcs-java-basic/src/main/java com.eomcs.io.ex04
git/eomcs-java-basic/src/main/java com.eomcs.io.ex05
git/eomcs-java-basic/src/main/java com.eomcs.io.ex06
git/eomcs-java-basic/src/main/java com.eomcs.io.ex07
git/eomcs-java-basic/src/main/java com.eomcs.io.ex08
DataIntputStream / DataOutputStream
FileInputStream / FileOutputStream으로 primitive type 입출력
FileInputStream / FileOutputStream으로 primitive type <-> byte 변환을 거쳐 파일에 입출력하고 싶다면, 직접 변환을 해줘야 한다.
가령, 즉 4바이트의 크기를 가진 int을 byte로 변환하여 FileOutputStream의 write() 메서드를 통해 출력하고 싶다면 int 값을 네 개의 byte로 나눠 각각 할당해줘야한다.
1111_1010_0101_0000_0011_1100_1000_0001 - int
1. 1111_1010 - byte
2. 0101_0000 - byte
3. 0011_1100 - byte
4. 1000_0001 - byte
이러한 변환과정을 직접 거쳐서 만들어진 byte 네 개를 차례대로 파일에 출력한다.
import java.io.FileOutputStream;
public class Exam0210 {
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("temp/test3.data");
int money = 1_3456_7890; // = 0x080557d2
out.write(money >> 24); // 00000008|0557d2
out.write(money >> 16); // 00000805|57d2
out.write(money >> 8); // 00080557|d2
out.write(money); // 080557d2
out.close();
System.out.println("데이터 출력 완료!");
}
}
반대로 FileInputStream의 read() 메서드를 통해 네 개의 바이트를 읽어 int 값으로 변환하고 싶다면, 네개의 바이트를 읽은 후, 읽은 차례대로 하나의 int 값에 겹치지 않게 더해주는 과정을 거쳐야 한다.
1. 1111_1010 - byte
2. 0101_0000 - byte
3. 0011_1100 - byte
4. 1000_0001 - byte
1111_1010_0101_0000_0011_1100_1000_0001 - int
import java.io.FileInputStream;
import java.io.IOException;
public class Exam0220 {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("temp/test3.data");
int value = in.read() << 24;
value += (in.read() << 16);
value += (in.read() << 8);
value += in.read();
in.close();
System.out.printf("%x\n", value);
}
}
long <-> byte 변환을 할 때도 int <-> byte와 동일한 방법으로 하면 된다.
만약 FileInputStream / FileOutputStream으로 String <-> byte 변환 과정을 거쳐 문자를 입출력하고 싶다면, 입출력할 문자의 인코딩 정보를 염두에 두고 올바르게 변환해주어야한다. 자세한 과정은 18일자 수업에서 관련 내용을 배웠기 때문에 생략한다.
FileInputStream / FileOutputStream으로 파일 포맷에 따라 바이트 입출력하기
public class Member {
String name;
int age;
boolean gender; // true(여자), false(남자)
@Override
public String toString() {
return "Member [name=" + name + ", age=" + age + ", gender=" + gender + "]";
}
}
위와 같이 필드를 갖는 Member 인스턴스 정보를 담는 파일을 만들려고 한다. Member 인스턴스가 갖는 정보는 Member의 인스턴스 필드에 담긴 primitive type 값들을 의미한다. 이 정보들을 모두 byte로 변환하여 파일에 출력해야 한다.
- name : String -> byte (UTF-8, 1~3byte)
- age : int -> byte (4byte)
- gender : boolean -> byte (1, 0, 1byte)
[이름 바이트 수(1)] [이름(n)] [나이(4)] [성별(1)]
String을 byte로 변환할 때에는, 나중에 다시 정보를 입력받을 때 String에 해당하는 바이트들을 몇개나 읽어야 하는지에 대한 정보가 필요하므로 String에 해당하는 byte 의 개수를 담는 1바이트를 String에 해당하는 byte들 앞에 추가한다.
이 포맷에 따라 FilOutputStream의 write() 메서드를 통해 직접 바이트를 하나씩 입력한다.
import java.io.FileOutputStream;
public class Exam0110 {
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("temp/test4.data");
Member member = new Member();
member.name = "AB가각간";
member.age = 27;
member.gender = true;
// 1) 이름 출력
byte[] bytes = member.name.getBytes("UTF-8");
out.write(bytes.length); // 1 바이트
out.write(bytes); // 문자열 바이트
// 2) 나이 출력 (4바이트)
out.write(member.age >> 24);
out.write(member.age >> 16);
out.write(member.age >> 8);
out.write(member.age);
// 3) 성별 출력 (1바이트)
if (member.gender)
out.write(1);
else
out.write(0);
out.close();
System.out.println("데이터 출력 완료!");
}
}
이렇게 출력된 파일을 FileInputStream 의 read() 메서드를 통해 파일 포맷에 따라 나누어 읽어들인 후, 새로 생성된 Member 객체의 필드로 채워 줄 수 있다.
import java.io.FileInputStream;
public class Exam0120 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/test4.data");
Member member = null;
member = new Member();
// 1) 이름 읽기
int size = in.read(); // 이름이 저장된 바이트 배열의 수
byte[] buf = new byte[size];
in.read(buf); // 이름 배열 개수 만큼 바이트를 읽어 배열에 저장한다.
member.name = new String(buf, "UTF-8");
// 2) 나이(int) 읽기
member.age = in.read() << 24;
member.age += in.read() << 16;
member.age += in.read() << 8;
member.age += in.read();
// 3) 성별 읽기
if (in.read() == 1)
member.gender = true;
else
member.gender = false;
in.close();
System.out.printf("%s\n", member);
}
}
DataOutputStream / DataInputStream으로 primitive type 값 입출력
DataOutputStream / DataInputStream은 이 primitive <-> byte 변환과정을 내부에서 자동으로 거치기 때문에 이 도구를 사용하면 직접 변환할 필요가 없다.
DataOutputStream / DataInputStream이 FileOutputStream / FileInputStream을 상속받아 만들어졌다면, 다음과 같은 메서드를 정의할 것이다.
DataOutputStream의 메서드
- writeUTF(String) : String 객체를 UTF-8 변환 방식에 따라 바이트 배열로 만들고 배열의 길이(1바이트)와 배열의 요소들(n바이트)을 출력한다.
- writeInt(int) : int 값을 4 개의 바이트로 나누어 출력한다.
- writeLong(long) : long 값을 8개의 바이트로 나누어 출력한다.
- writeBoolean(boolean) : boolean 값을 출력한다. (true -> 1, false -> 0)
각 메서드에서는 직접 데이터를 파일에 출력하지 않고 상위 클래스인 FileOutputStream의 write 메서드를 호출하며, DataOutputStream은 출력할 값을 변환해주는 역할만 수행한다.
import java.io.FileOutputStream;
public class DataOutputStream extends FileOutputStream {
public DataOutputStream(String filename) throws Exception {
super(filename);
}
public void writeUTF(String str) throws Exception {
byte[] bytes = str.getBytes("UTF-8");
this.write(bytes.length);
this.write(bytes);
}
public void writeInt(int value) throws Exception {
this.write(value >> 24);
this.write(value >> 16);
this.write(value >> 8);
this.write(value);
}
public void writeLong(long value) throws Exception {
this.write((int)(value >> 56));
this.write((int)(value >> 48));
this.write((int)(value >> 40));
this.write((int)(value >> 32));
this.write((int)(value >> 24));
this.write((int)(value >> 16));
this.write((int)(value >> 8));
this.write((int)value);
}
public void writeBoolean(boolean value) throws Exception {
if (value)
this.write(1);
else
this.write(0);
}
}
DataInputStream의 메서드
- readUTF(String) : 첫 바이트에 저장된 수만큼의 바이트들을 읽어 들이고, 이를 UTF-8로 취급하여 변환한 String 객체를 리턴한다.
- readInt(int) : 파일에 있는 4 개의 바이트를 읽어 하나의 int 값으로 저장하여 리턴한다.
- readLong(long) : 파일에 있는 8 개의 바이트를 읽어 하나의 long 값으로 저장하여 리턴한다.
- readBoolean(boolean) : 1바이트를 읽고 1이면 true, 0이면 false를 리턴한다.
DataOuputStream과 마찬가지로 직접 데이터를 파일로부터 입력받지 않고 상위 클래스인 FileInputStream의 read 메서드를 호출하며, DataInputStream은 입력받은 값을 적절한 primitive type으로 변환하는 역할만 수행한다.
import java.io.FileInputStream;
public class DataInputStream extends FileInputStream {
public DataInputStream(String filename) throws Exception {
super(filename);
}
public String readUTF() throws Exception {
int size = this.read();
byte[] bytes = new byte[size];
this.read(bytes); // 이름 배열 개수 만큼 바이트를 읽어 배열에 저장한다.
return new String(bytes, "UTF-8");
}
public int readInt() throws Exception {
int value = 0;
value = this.read() << 24;
value += this.read() << 16;
value += this.read() << 8;
value += this.read();
return value;
}
public long readLong() throws Exception {
long value = 0;
value += (long) this.read() << 56;
value += (long) this.read() << 48;
value += (long) this.read() << 40;
value += (long) this.read() << 32;
value += (long) this.read() << 24;
value += (long) this.read() << 16;
value += (long) this.read() << 8;
value += this.read();
return value;
}
public boolean readBoolean() throws Exception {
if (this.read() == 1)
return true;
else
return false;
}
}
이 객체들을 사용하여 Member 객체의 정보를 파일에 입출력하는 과정을 FileOutputStream / FileInputStream을 쓸 때보다 간결하게 할 수 있다.
- Member 객체 출력
public class Exam0210 {
public static void main(String[] args) throws Exception {
DataOutputStream out = new DataOutputStream("temp/test4_2.data");
Member member = new Member();
member.name = "AB가각간";
member.age = 27;
member.gender = true;
// 1) 이름 출력
out.writeUTF(member.name);
// 2) 나이 출력 (4바이트)
out.writeInt(member.age);
// 3) 성별 출력 (1바이트)
out.writeBoolean(member.gender);
out.close();
System.out.println("데이터 출력 완료!");
}
}
- Member 객체 입력
public class Exam0220 {
public static void main(String[] args) throws Exception {
DataInputStream in = new DataInputStream("temp/test4_2.data");
Member member = null;
member = new Member();
// 1) 이름 읽기
member.name = in.readUTF();
// 2) 나이(int) 읽기
member.age = in.readInt();
// 3) 성별 읽기
member.gender = in.readBoolean();
in.close();
System.out.printf("%s\n", member);
}
}
BufferedInputStream / BufferedOutputStream
한번에 한 바이트를 입출력하는 것보다는 임시 공간으로 쓸 배열을 만들어서 한번에 여러 바이트를 입출력하는 것이 더 빠를 수 있다. 입출력을 위해 파일에 접근하는 과정 자체에 시간이 어느정도 걸리기 때문이다. 버퍼를 사용하기 전과 사용한 후의 실행 속도 차이는 다음과 같다.
버퍼 사용 전 파일 읽기
jls11.pdf 파일의 총 용량은 3,336,294 bytes이다. 이 파일을 처음부터 끝까지 매 바이트마다 read()를 호출(3,336,294번 호출)하여 읽는 데 걸리는 시간을 구하면, 3227 밀리초 즉 3.2초 정도가 걸린다.
import java.io.FileInputStream;
public class Exam0110 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls11.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((b = in.read()) != -1) {
callCount++; // 파일을 끝까지 읽는다.
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
// 결과
// 3227
// 3336294
버퍼 사용 후 파일 읽기
8KB 정도 크기의 바이트 배열을 준비하여 한번에 배열 크기만큼 파일을 읽으면 read() 호출 수가 408번으로 줄고, 모두 읽는데, 1 밀리초가 걸린다. 즉 버퍼를 사용해 한번에 많은 바이트를 읽는 것이 매번 읽을때마다 메서드를 호출하는 것보다 더 빠르다.
import java.io.FileInputStream;
public class Exam0120 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls11.pdf");
byte[] buf = new byte[8192]; // 보통 8KB 정도 메모리를 준비한다.
int len = 0;
long startTime = System.currentTimeMillis(); // 밀리초
int callCount = 0;
while ((len = in.read(buf)) != -1) {
callCount++; // 파일을 끝까지 읽는다.
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
System.out.println(callCount);
in.close();
}
}
단, 버퍼의 크기를 무조건 크게 잡는다고 더 빨라지는 것은 아니다. 한번에 4,000,000 바이트를 읽도록 하여 메서드를 단 한번만 호출한다하더라도 2밀리초가 걸린다.
버퍼 사용 전 파일 쓰기
버퍼를 사용하지 않고 jls11.pdf 파일을 처음부터 끝까지 매 바이트마다 read()를 호출하여 읽은것을 바로 다른 파일에 그대로 출력하는 데 거리는 시간은 13109 밀리초, 즉 약 13.1초이다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Exam0210 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls11.pdf");
FileOutputStream out = new FileOutputStream("temp/jls11_2.pdf");
int b;
long startTime = System.currentTimeMillis(); // 밀리초
while ((b = in.read()) != -1) {
out.write(b);
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
in.close();
out.close();
}
}
// 결과 !
// 13109
버퍼 사용 후 파일 쓰기
8KB 크가의 배열을 준비하고 한번에 배열 크기 만큼 읽은 것을 그대로 다른 파일에 출력하는 데 걸리는 시간은 4밀리 초이다. 즉 버퍼를 이용해서 한번에 여러 바이트를 읽고 쓰는 것이 매 바이트마다 메서드를 호출하는 것보다 훨씬 빠르다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Exam0220 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/jls11.pdf");
FileOutputStream out = new FileOutputStream("temp/jls11_3.pdf");
byte[] buf = new byte[8192]; // 보통 8KB 정도 메모리를 준비한다.
int len = 0;
long startTime = System.currentTimeMillis(); // 밀리초
while ((len = in.read(buf)) != -1)
out.write(buf, 0, len);
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
in.close();
out.close();
}
}
BufferedInputStream / BufferedOutputStream 사용
BufferedInputStream / BufferedOutputStream는 파일 입출력 시 갖고 있는 임시공간을 사용하여 FileInputStream / FileOutputStream보다 훨씬 효율적으로 파일을 입출력할 수 있다.
BufferedInputStream / BufferedOutputStream가 만약 FileInputStream / FileOutputStream을 상속받았다면 다음과 같이 정의될 것이다.
BufferredOutputStream의 필드
- 8KB 크기의 바이트 배열(버퍼) buf
- 버퍼에 실제로 담은 바이트의 개수를 뜻하는 cursor
BufferedOutputStream의 메서드
- write(int) - 버퍼에 하나씩 바이트를 저장하며 버퍼가 꽉 차면 한꺼번에 출력
- write(byte[]) - 파라미터로 받은 배열에 저장된 바이트들을 버퍼로 한번에 옮기며 버퍼가 꽉 차면 한꺼번에 출력
- flush() - 버퍼에 있는 남은 요소들을 모두 출력
- close() - flush() 호출 후, 상위 클래스의 close() 호출
BufferedOutputStream은 버퍼가 꽉 찰 때만 파일로 출력하기 때문에 버퍼가 꽉 차지 않아도 남은 요소를 파일에 모두 출력하여 퍼버를 비우는 flush라는 메서드가 존재한다. 출력의 마지막에 다 출력이 온전히 되지 않는 경우를 대비하여 항상 flush를 호출하는 것이 좋다.
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedOutputStream extends FileOutputStream {
byte[] buf = new byte[8196];
int cursor;
public BufferedOutputStream(String filename) throws Exception {
super(filename);
}
@Override
public void write(int b) throws IOException {
if (cursor == buf.length) {
super.write(buf);
cursor = 0;
}
buf[cursor++] = (byte) b;
}
@Override
public void write(byte[] buf) throws IOException {
for (byte b : buf) {
this.write(b & 0x000000ff);
}
}
@Override
public void close() throws IOException {
this.flush();
super.close();
}
@Override
public void flush() throws IOException {
if (cursor > 0)
this.write(buf, 0, cursor);
cursor = 0;
}
}
BufferredInputStream의 필드
- 8KB 크기의 바이트 배열(버퍼)를 필드로 갖는다
- 입력받아 버퍼에 담은 바이트들의 개수를 의미하는 size
- 버퍼에서 마지막으로 조회한 바이트의 위치
BufferedInputStream의 메서드
- read() - 버퍼가 비어있거나, 버퍼에 있는 모든 바이트를 읽었다면 버퍼의 크기만큼 모두 입력받은 후, 커서에 위치한 요소를 읽는다. 리턴 값이 int 이므로 의도된 수가 온전히 변환될 수 있도록 0xff를 &한다.
- read(byte[]) - 버퍼가 비어있거나, 버퍼에 있는 모든 바이트를 읽었다면 버퍼의 크기만큼 모두 입력받은 후, 커서에서부터 파라미터로 받은 배열의 크기만큼 읽어 배열에 담는다. 실제로 읽은 바이트의 개수를 리턴한다.
import java.io.FileInputStream;
import java.io.IOException;
public class BufferedInputStream extends FileInputStream {
byte[] buf = new byte[8192];
int size;
int cursor;
public BufferedInputStream(String filename) throws Exception {
super(filename);
}
@Override
public int read() throws IOException {
if (cursor == size) {
if ((size = super.read(buf)) == -1) {
return -1;
}
cursor = 0;
}
return buf[cursor++] & 0x000000ff;
}
@Override
public int read(byte[] buf) throws IOException {
int i = 0;
for (; i < buf.length; i++) {
int b = this.read();
if (b == -1) {
break;
}
buf[i] = (byte) b;
}
return i;
}
}
ByteArrayInputStream / ByteArrayOutputStream
FileInputStream / FileOutputStream과 같이 파일에서 데이터를 입출력 하는 도구 이외에도 그냥 메모리에 있는 바이트 배열을 입출력하는 도구도 있다. 그냥 배열을 조회하거나 직접 추가할 수도 있겠지만, 파일 입출력과의 일관성을 위해 바이트 배열을 데이터 저장소로 간주하여 read(), write() 라는 일정한 규칙을 바이트 배열에도 적용한 셈이다. 사용 방법은 FileInputStream / FileOutputStream과 유사하다.
import java.io.ByteArrayInputStream;
public class Exam0110 {
public static void main(String[] args) throws Exception {
byte[] buf = {
0x0b, 0x41, 0x42, (byte) 0xea, (byte) 0xb0, (byte) 0x80, (byte) 0xea, (byte) 0xb0,
(byte) 0x81, (byte) 0xea, (byte) 0xb0, (byte) 0x84, 0x00, 0x00, 0x00, 0x1b, 0x01};
ByteArrayInputStream in = new ByteArrayInputStream(buf);
int b;
while (true) {
b = in.read();
if (b == -1)
break;
System.out.printf("%x ", b);
}
System.out.println();
in.close();
}
}
그런데 이 도구들에도 버퍼를 사용해 더 빨리 입출력하고자 하거나, 데이터를 알맞게 변환하여 사용하고자 할 때, 이에 해당하는 보조도구들이 이미 파일 입출력 도구를 상속받고 있기 때문에 사용할 수가 없다. 따라서 ByteArrayInputStream / ByteArrayOutputStream 전용 보조도구들을 따로 생성해줘야 한다.
예를 들어 배열에서 byte들을 읽은 후 primitive data 로 알맞게 변환하는 DataByteArrayInputStream의 코드는 ByteArrayInputStream을 상속받으며 다음과 같이 정의될 것이다. 상속 부분을 제외하고는 기존의 DataInputStream과 코드가 중복된다.
import java.io.ByteArrayInputStream;
public class DataByteArrayInputStream extends ByteArrayInputStream {
public DataByteArrayInputStream(byte[] buf) {
super(buf);
}
public String readUTF() throws Exception {
// 상속 받은 read() 메서드를 사용하여 문자열 출력
int size = this.read();
byte[] bytes = new byte[size];
this.read(bytes); // 배열 개수 만큼 바이트를 읽어 배열에 저장한다.
return new String(bytes, "UTF-8");
}
public int readInt() throws Exception {
// 상속 받은 read() 메서드를 사용하여 int 값 출력
int value = 0;
value = this.read() << 24;
value += this.read() << 16;
value += this.read() << 8;
value += this.read();
return value;
}
public long readLong() throws Exception {
// 상속 받은 read() 메서드를 사용하여 long 값 출력
long value = 0;
value += (long) this.read() << 56;
value += (long) this.read() << 48;
value += (long) this.read() << 40;
value += (long) this.read() << 32;
value += (long) this.read() << 24;
value += (long) this.read() << 16;
value += (long) this.read() << 8;
value += this.read();
return value;
}
public boolean readBoolean() throws Exception {
// 상속 받은 read() 메서드를 사용하여 boolean 값 출력
if (this.read() == 1)
return true;
else
return false;
}
}
데코레이터(Decorator) 패턴
객체의 기능 확장을 위한 방법 중 하나로 클래스들 사이의 상속 관계 대신 포함관계를 만들어 객체의 원하는 기능을 장신구처럼 탈부착 식으로 사용할 수 있게 하는 디자인 패턴이다. 추가할 수 있는 기능들의 종류가 많은 경우, 상속을 통해 확장하면 너무 많은 클래스가 생긴다는 단점을 커버하기 위해 포함 관계를 사용했다.
만약 입출력 도구들과 보조 도구들이 상속 관계를 하고 있었다면, 각 입출력 도구에 대한 보조도구들이 한개씩 있어야하므로 클래스가 불필요하게 많아질 것이다.
데이터를 읽고 쓰는 도구
- FileInputStream / FileOutputStream : 파일 저장소에서 데이터를 읽고 쓰는 도구
- ByteArrayInpuStream / ByteArrrayOutputStream : 바이트 배열 저장소에서 데이터를 읽고 쓰는 도구
입출력 도구에 추가되는 보조 도구
- DataFileInputStream / DataFileOutputStream / DataByteArrayInputStream / DataByteArrayOutputStream : byte <-> int, long, String(UTF-8), boolean 편리하게 변환하는 기능
- BufferedFileInputStream / BufferedFileOutputStream / BufferedByteArrayInputStream / BufferedByteArrayOutputStream : 버퍼를 추가하여 데이터 입출력 시간을 크게 줄이는 기능
예를 들어, BufferedFileInputStream / BufferedByteArrayInputStream이 각각을 상속받고 있는데 코드가 완전히 같다. 이 중복된 코드를 갖는 클래스들을 개선하기 위해서 보조도구들이 입출력 도구들을 상속받지 않고 둘 중에 원하는 기능을 골라 포함할 수 있도록 하면 중복된 코드를 하나로 통일할 수 있다.
데이터를 읽고 쓰는 도구
- FileInputStream / FileOutputStream : 파일 저장소에서 데이터를 읽고 쓰는 도구
- ByteArrayInpuStream / ByteArrrayOutputStream : 바이트 배열 저장소에서 데이터를 읽고 쓰는 도구
입출력 도구에 추가되는 보조 도구
- DataInputStream / DataOutputStream : byte <-> int, long, String(UTF-8), boolean 편리하게 변환하는 기능
- BufferedInputStream / BufferedOutputStream : 버퍼를 추가하여 데이터 입출력 시간을 크게 줄이는 기능
이제 입출력 도구에 추가되는 기능은 데이터를 읽고 쓰는 도구를 상속받지 않고 포함하므로 보조도구들은 한 클래스만으로도 자유롭게 파일을 입출력하는 도구와 바이트 배열을 입출력하는 도구 중 원하는 대로 사용할 수가 있다.
이런 구조로 바꾸게 되면 파일 저장소를 입력하고자 할 때, FileInputStream 객체를 생성하고, 이 객체에 대해서 추가하고 싶은 기능을 골라서 생성할 때 FileInputStream 객체를 파라미터로 주어 생성자를 호출해주기만 하면 원하는 기능을 마음대로 원하는 도구에 장착할 수가 있다.
'국비 교육' 카테고리의 다른 글
2020.10.5일자 수업 : connectionless, HTTP, URL, base64 (0) | 2020.10.06 |
---|---|
2020.9.18 일자 수업 : 캐릭터 스트림 (0) | 2020.10.02 |
2020.9.17 일자 수업 : 파일 입출력, 바이트 스트림, 캐릭터 스트림 (0) | 2020.10.01 |