12. 자바의 입출력을 위한 I/O 스트림
입출력 스트림
- 자바는 다양한 입출력 장치에 독립적으로 일관성있는 입출력을 입출력 스트림을 통해 제공
- 입출력이 구현되는 곳: 파일 디스크, 키보드, 마우스, 네트웍, 메모리 등 모든 자료가 입력되고 출력되는 곳
입출력 스트림의 구분
- 대상 기준 : 입력 스트림 / 출력 스트림
- 자료의 종류 : 바이트 스트림 / 문자 스트림
- 기능 : 기반 스트림 / 보조 스트림
입력 스트림과 출력 스트림 (byte기반 스트림)
종류 | 예시 |
입력 스트림 | FileInputStream, FileReader, BufferedInputStream, BufferedReader 등 |
출력 스트림 | FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter 등 |
기반 스트림과 보조 스트림
- 기반 스트림 : 대상에 직접 자료를 읽고 쓰는 기능의 스트림
- 보조 스트림 : 직접 읽고 쓰는 기능은 없이 추가적인 기능을 더해주는 스트림
종류 | 예시 |
입력 스트림 | FileInputStream, FileOutputStream, FileReader, FileWriter 등 |
출력 스트 | InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream 등 |
// 1. 기반스트림 생성
FileInputStream fileStream = new FileInputStream("text.txt");
// 2. 기반 스트림을 이용해서 보조스트림을 생성한다.
BufferedInputStream bis = new BufferedInputStream(fileStream);
// 3. 보조스트림인 BufferedInputStream 으로부터 데이터를 읽는다.
bis.read();
13. 표준 입출력 스트림
System 클래스의 표준 입출력 멤버
public class System{
public static PrintStream out;
public static InputStream in;
public static PrintStream err;
}
- System.out
- 표준 출력(모니터) 스트림
System.out.println("출력 메세지");
- System.in
- 표준 입력(키보드) 스트림
int d = System.in.read()
// 한 바이트 읽기
- System.err
- 표준 에러 출력(모니터) 스트림
System.err.println("에러 메세지");
System.in 사용하기 예제
public class SystemInTest1 {
public static void main(String[] args) {
System.out.println("알파벳 하나를 쓰고 [Enter]를 누르세요");
int i;
try {
i = System.in.read();
System.out.println(i);
System.out.println((char)i);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class SystemInTest2 {
public static void main(String[] args) {
System.out.println("알파벳 여러 개를 쓰고 [Enter]를 누르세요");
int i;
try {
while( (i = System.in.read()) != '\n' ) {
System.out.print((char)i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
14. 바이트 단위 입출력 스트림
InputStream
바이트 단위 입력 스트림 최상위 추상 클래스
-
주요 하위 클래스
FileInputStream 파일에서 바이트 단위로 자료를 읽습니다. ByteArrayInputStream byte 배열 메모리에서 바이트 단위로 자료를 읽습니다. FilterInputStream 기반 스트림에서 자료를 읽을 때 추가 기능을 제공하는 보조 스트림의 상위 클래스 -
주요 메서드
int read() 입력 스트림으로부터 한 바이트의 자료를 읽습니다. 읽은 자료의 바이트 수를 반환합니다. int read(byte b[]) 입력 스트림으로 부터 b[] 크기의 자료를 b[]에 읽습니다. 읽은 자료의 바이트 수를 반환합니다. int read(byte b[], int off, int len) 입력 스트림으로 부터 b[] 크기의 자료를 b[]의 off변수 위치부터 저장하며 len 만큼 읽습니다. 읽은 자료의 바이트 수를 반환합니다. void close() 입력 스트림과 연결된 대상 리소스를 닫습니다.
FileInputStream 예제
public class FileInputStreamTest1 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("input.txt");
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
} catch (IOException e) {
System.out.println(e);
} finally{
try {
fis.close();
} catch (IOException e) {
System.out.println(e);
} catch (NullPointerException e){
System.out.println(e);
}
}
System.out.println("end");
}
}
public class FileInputStreamTest2 {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("input.txt")){
int i;
while ( (i = fis.read()) != -1){
System.out.println((char)i);
}
System.out.println("end");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class FileInputStreamTest3 {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input2.txt")){
byte[] bs = new byte[10];
int i;
while ( (i = fis.read(bs)) != -1){
/*for(byte b : bs){
System.out.print((char)b);
}*/
for(int k= 0; k<i; k++){
System.out.print((char)bs[k]);
}
System.out.println(": " +i + "바이트 읽음" );
}
/*while ( (i = fis.read(bs, 1, 9)) != -1){
for(int k= 0; k<i; k++){
System.out.print((char)bs[k]);
}
System.out.println(": " +i + "바이트 읽음" );
}*/
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
OutputStream
바이트 단위 출력 스트림 최상위 추상 클래스
-
주요 하위 클래스
FileOutputStream 파일에서 바이트 단위로 자료를 씁니다. ByteArrayOutputStream byte 배열 메모리에서 바이트 단위로 자료를 씁니다. FilterOutputStream 기반 스트림에서 자료를 쓸 때 추가 기능을 제공하는 보조 스트림의 상위 클래스 -
주요 메서드
int write() 한 바이트를 출력합니다. int write(byte b[]) b[] 크기의 자료를 출력합니다. int write(byte b[], int off, int len) b[] 배열에 있는 자료의 off 위치부터 len 개수만큼 자료를 출력합니다. void flush() 출력을 위해 잠시 자료가 머무르는 출력 버퍼를 강제로 비워 자료를 출력합니다. void close() 출력 스트림과 연결된 대상 리소스를 닫습니다. 출력 버퍼가 비워집니다.
FileOutputStream 예제
public class FileOutputStreamTest1 {
public static void main(String[] args) {
try(FileOutputStream fos = new FileOutputStream("output.txt")){
fos.write(65); //A
fos.write(66); //B
fos.write(67); //C
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("출력이 완료되었습니다.");
}
}
public class FileOutputStreamTest2 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("output2.txt",true);
try(fos){ //java 9 부터 제공되는 기능
byte[] bs = new byte[26];
byte data = 65; //'A' 의 아스키 값
for(int i = 0; i < bs.length; i++){ // A-Z 까지 배열에 넣기
bs[i] = data;
data++;
}
fos.write(bs); //배열 한꺼번에 출력하기
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("출력이 완료되었습니다.");
}
}
public class FileOutputStreamTest3 {
public static void main(String[] args) {
try(FileOutputStream fos = new FileOutputStream("output3.txt"))
{
byte[] bs = new byte[26];
byte data = 65; //'A' 의 아스키 값
for(int i = 0; i < bs.length; i++){ // A-Z 까지 배열에 넣기
bs[i] = data;
data++;
}
fos.write(bs, 2, 10); // 배열의 2 번째 위치부터 10 개 바이트 출력하기
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("출력이 완료되었습니다.");
}
}
15. 문자 단위 입출력 스트림
Reader
문자 단위 입력 스트림 최상위 추상 클래스
-
주요 하위 클래스
FileReader 파일에서 문자 단위로 읽는 스트림 클래스입니다. InputStreamReader 바이트 단위로 읽은 자료를 문자로 변환해주는 보조 스트림 클래스 입니다. BufferedReader 문자로 읽을 때 배열을 제공하여 한꺼번에 읽을 수 있는 기능을 제공하는 보조 스트림입니다. -
주요 메서드
int read() 파일로부터 한 문자를 읽습니다. 읽은 문자를 반환합니다. int read(char[] buf) 파일로부터 buf 배열에 문자를 읽습니다. int read(char[] buf, int off, int len) 파일로부터 buf 배열의 off 위치로부터 len 개수만큼의 문자를 읽습니다. void close() 입력 스트림과 연결된 대상 리소스를 닫습니다.
FileReader
public class FileReaderTest {
public static void main(String[] args) {
try(FileReader fr = new FileReader("reader.txt")){
int i;
while( (i = fr.read()) != -1){
System.out.print((char)i);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
Writer
문자 단위 출력 스트림 최상위 추상 클래스
-
주요 하위 클래스
FileWriter 파일에서 문자 단위로 출력하는 스트림 클래스입니다. OutputStreamWriter 바이트 단위의 자료를 문자로 변환해 출력해주는 보조 스트림 클래스 입니다. BufferedWriter 문자로 쓸 때 배열을 제공하여 한꺼번에 쓸 수 있는 기능을 제공하는 보조 스트림 -
메서드
int write(int c) 한 문자를 파일에 씁니다. int write(char[] buf) 문자 배열 buf의 내용을 출력합니다. int write(char[] buf, int off, int len) 문자 배열 buf의 off위치에서부터 len 개수의 문자를 출력 int write(String str) 문자열 str을 출력합니다. int write(String str, int off, int len) 문자열 str의 off번째 문자로부터 len 개수만큼 출력합니다. int flush() 출력하기 전에 자료가 있는 공간(출력 버퍼)을 비워 출력하도록 합니다 void close() 스트림과 연결된 리소스를 닫습니다. 출력 버퍼도 비워집니다.
FileWriter
public class FileWriterTest {
public static void main(String[] args) {
try(FileWriter fw = new FileWriter("writer.txt")){
fw.write('A'); // 문자 하나 출력
char buf[] = {'B','C','D','E','F','G'};
fw.write(buf); //문자 배열 출력
fw.write("안녕하세요. 잘 써지네요"); //String 출력
fw.write(buf, 1, 2); //문자 배열의 일부 출력
fw.write("65"); //숫자를 그대로 출력
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("출력이 완료되었습니다.");
}
}
16. 여러가지 보조 스트림 클래스들
보조 스트림
- 실제 읽고 쓰는 스트림이 아닌 보조 기능을 제공하는 스트림
FilterInputStream
과FilterOutputStream
이 보조 스트림의 상위 클래스들- 생성자의 매개변수로 또 다른 스트림(기반 스트림이나 다른 보조 스트림)을 가짐
Decorator Pattern
으로 구현 됨
InputStreamReader와 OutputStreamWriter
바이트 단위로 읽거나 쓰는 자료를 문자로 변환해주는 보조 스트림
public class InputStreamReaderTest {
public static void main(String[] args) {
try(InputStreamReader isr = new InputStreamReader(new FileInputStream("reader.txt"))){
int i;
while( (i = isr.read()) != -1){ //보조 스트림으로 읽습니다.
System.out.print((char)i);
}
}catch(IOException e) {
e.printStackTrace();
}
}
}
BufferedInputStream과 BufferedOutputStream
약 8k의 배열이 제공되어 입출력이 빠르게 하는 기능이 제공되는 보조 스트림
BufferedReader
와 BufferedWriter
는 문자용 입출력 보조 스트림
public class BufferedStreamTest {
public static void main(String[] args) {
long millisecond = 0;
try(FileInputStream fis = new FileInputStream("a.zip");
FileOutputStream fos = new FileOutputStream("copy.zip");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos)){
millisecond = System.currentTimeMillis();
int i;
while( ( i = bis.read()) != -1){
bos.write(i);
}
millisecond = System.currentTimeMillis() - millisecond;
}catch(IOException e) {
e.printStackTrace();
}
System.out.println("파일 복사 하는 데 " + millisecond + " milliseconds 소요되었습니다.");
}
}
DataInputStream과 DataOutputStream
자료가 메모리에 저장된 상태 그대로 읽거나 쓰는 스트림
public class DataStreamTest {
public static void main(String[] args) {
try(FileOutputStream fos = new FileOutputStream("data.txt");
DataOutputStream dos = new DataOutputStream(fos))
{
dos.writeByte(100);
dos.writeChar('A');
dos.writeInt(10);
dos.writeFloat(3.14f);
dos.writeUTF("Test");
}catch(IOException e) {
e.printStackTrace();
}
try(FileInputStream fis = new FileInputStream("data.txt");
DataInputStream dis = new DataInputStream(fis))
{
System.out.println(dis.readByte());
System.out.println(dis.readChar());
System.out.println(dis.readInt());
System.out.println(dis.readFloat());
System.out.println(dis.readUTF());
}catch (IOException e) {
e.printStackTrace();
}
}
}
17. 직렬화 (serialization)
serialization 이란?
인스턴스의 상태를 그대로 파일 저장하거나 네트웍으로 전송하고 (serialization) 이를 다시 복원(deserialization) 하는 방식
📌 자바 직렬화란?
자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록
바이트(byte) 형태로 데이터 변환하는 기술과
바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)을 아울러서 이야기합니다.
📌 시스템적으로 이야기하자면
JVM(Java Virtual Machine 이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과
직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 같이 이야기합니다.
ObjectInputStream(InputStream in) | InputStream을 생성자의 매개변수로 받아 ObjectInputStream을 생성 |
ObjectOutputStream(OutputStream out) | OutputStream을 생성자의 매개변수로 받아 ObjectOutputStream을 생성 |
자바 직렬화 조건
⛔ 자바 기본(primitive) 타입과
java.io.Serializable
인터페이스를 상속받은 객체는 직렬화 할 수 있는 기본 조건을 가집니다.
자바 직렬화는 언제 어디서 사용되나요?
JVM의 메모리에서만 상주되어있는 객체 데이터를 그대로 영속화(Persistence)가 필요할 때 사용
네트워크로 전송도 가능
- 서블릿 세션 (Servlet Session)
- 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됨
- 캐시 (Cache)
- DB를 조회한 후 가져온 데이터 객체 같은 경우 실시간 형태로 요구하는 데이터가 아니라면 -저장된 객체를 찾아서 응답하게 하는 형태를 보통 캐시를 사용함
- 캐시를 이용하면 DB에 대한 리소스를 절약할 수 있기 때문에 많은 시스템에서 자주 활용됨
- 캐시 할 부분을 자바 직렬화된 데이터를 저장해서 사용
- 자바 RMI
- 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술
자바 직렬화를 사용하는 이유
자바 직렬화는 같은 자바 시스템에서의 데이터 전송(다른 JVM 환경의 시스템으로의 전송), 저장에 최적화 되어있다.
데이터 구조가 복잡하더라도 직렬화의 기본 조건만 지키면, 바로 직렬화와 역직렬화가 가능
데이터 타입이 자동으로 맞춰지기 때문에, 역직렬화 시 바로 기존 객체처럼 사용가능
직렬화 방법
java.io.ObjectOutputStream
객체를 이용
Member member = new Member("김배민", "deliverykim@baemin.com", 25);
byte[] serializedMember;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(member);
// serializedMember -> 직렬화된 member 객체
serializedMember = baos.toByteArray();
}
}
// 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}
Serializable 인터페이스
직렬화는 인스턴스의 내용이 외부로 유출되는 것이므로 프로그래머가 해당 객체에 대한 직렬화 의도를 표시해야 함
transient
- 직렬화 하지 않으려는 멤버 변수에 사용함 (Socket등 직렬화 할 수 없는 객체)
transient private int age;
age 값을 나중에 바꿔도 객체에 저장되지 않음
class Person implements Serializable{
private static final long serialVersionUID = -1503252402544036183L;
String name;
String job;
public Person() {}
public Person(String name, String job) {
this.name = name;
this.job = job;
}
public String toString()
{
return name + "," + job;
}
}
public class SerializationTest {
public static void main(String[] args) throws ClassNotFoundException {
Person personAhn = new Person("이순신", "대표이사");
Person personKim = new Person("김유신", "상무이사");
try(FileOutputStream fos = new FileOutputStream("serial.out");
ObjectOutputStream oos = new ObjectOutputStream(fos)){
oos.writeObject(personAhn);
oos.writeObject(personKim);
}catch(IOException e) {
e.printStackTrace();
}
try(FileInputStream fis = new FileInputStream("serial.out");
ObjectInputStream ois = new ObjectInputStream(fis)){
Person p1 = (Person)ois.readObject();
Person p2 = (Person)ois.readObject();
System.out.println(p1);
System.out.println(p2);
}catch (IOException e) {
e.printStackTrace();
}
}
}
Externalizable 인터페이스
프로그래머가 직접 객체를 읽고 쓰는 코드를 구현 할 수 있음
writeExtenral()
과 readExternal()
메소드를 작성해야함
class Person implements Externalizable{
String name;
String job;
public Person() {}
public Person(String name, String job) {
this.name = name;
this.job = job;
}
public String toString()
{
return name + "," + job;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
//out.writeUTF(job);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
//job = in.readUTF();
}
}
18. 그 외 여러가지 입출력 클래스들
File 클래스
- 파일 개념을 추상화한 클래스
- 입출력 기능은 없고, 파일의 이름, 경로, 읽기 전용등의 속성을 알수 있음
public class FileTest {
public static void main(String[] args) throws IOException {
File file = new File("D:\\JAVA_LAB\\Chapter6\\newFile.txt");
file.createNewFile();
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.getName());
System.out.println(file.getAbsolutePath());
System.out.println(file.getPath());
System.out.println(file.canRead());
System.out.println(file.canWrite());
file.delete();
}
}
RandomAccessFile 클래스
- 입출력 클래스 중 유일하게 파일에 대한 입력과 출력을 동시에 할 수 있는 클래스
- 파일 포인터가 있어서 읽고 쓰는 위치의 이동이 가능함
public class RandomAccessFileTest {
public static void main(String[] args) throws IOException {
RandomAccessFile rf = new RandomAccessFile("random.txt", "rw");
rf.writeInt(100);
System.out.println("파일 포인터 위치:" + rf.getFilePointer());
rf.writeDouble(3.14);
System.out.println("파일 포인터 위치:" + rf.getFilePointer());
rf.writeUTF("안녕하세요");
System.out.println("파일 포인터 위치:" + rf.getFilePointer());
rf.seek(0);
System.out.println("파일 포인터 위치:" + rf.getFilePointer());
int i = rf.readInt();
double d = rf.readDouble();
String str = rf.readUTF();
System.out.println("파일 포인터 위치:" + rf.getFilePointer());
System.out.println(i);
System.out.println(d);
System.out.println(str);
}
}
19. 데코레이터 패턴을 활용한 커피 머신 프로그램
Decorator Pattern
패턴은 동작을 포함하는 특수 래퍼(wrapper) 객체 안에 객체를 배치해서 객체에 새 동작을 추가할 수 있는 구조적 디자인 패턴
- 자바의 입출력 스트림은
decorator pattern
임 - 여러 decorator들을 활용하여 다양한 기능을 제공
- 상속 보다 유연한 구현 방식
- 데코레이터는 다른 데코레이터나 또는 컴포넌트를 포함해야 함
- 지속적인 기능의 추가와 제거가 용이함
decorator
와component
는 동일한 것이 아님- 기반 스트림 클래스가 직접 읽고 쓸수 있음, 보조 스트림은 추가적인 기능 제공
📌 Component
실질적인 인스턴스를 컨트롤
📌 ConcreteComponent
Component의 실질적인 인스턴스의 부분으로 책임의 주체의 역할
📌 Decorator
Component와 ConcreteDecorator를 동일시 하도록 해주는 역할
📌 ConcreteDecoreator
실질적인 장식 인스턴스 및 정의이며 추가된 책임의 주체
커피를 만들어보아요~
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
-
task
Decorator Pattern을 활용하여 커피를 만들어 봅시다. 아메리카노 카페 라떼 = 아메리카노 + 우유 모카 커피 = 아메리카노 + 우유 + 모카시럽 크림 올라간 모카커피 = 아메리카노 + 우유 + 모카시럽 + whipping cream 커피는 컴포넌트고, 우유, 모카시럽, whipping cream은 모두 데코레이터임
-
abstract class Coffee
public abstract class Coffee { public abstract void brewing(); }
-
abstract class Decorator
extends Coffee
public abstract class Decorator extends Coffee{ Coffee coffee; public Decorator(Coffee coffee){ this.coffee = coffee; } @Override public void brewing() { coffee.brewing(); } }
-
class Latte
extends Decorator
public class Latte extends Decorator{ public Latte(Coffee coffee) { super(coffee); } public void brewing() { super.brewing(); System.out.print("Adding Milk "); } }
-
class Mocha
extends Decorator
public class Mocha extends Decorator{ public Mocha(Coffee coffee) { super(coffee); // TODO Auto-generated constructor stub } public void brewing() { super.brewing(); System.out.print("Adding Mocha Syrup "); } }
-
class WhippedCream
extends Decorator
public class WhippedCream extends Decorator{ public WhippedCream(Coffee coffee) { super(coffee); } public void brewing() { super.brewing(); System.out.print("Adding WhippedCream "); } }
-
class KenyaAmericano
extends Coffee
public class KenyaAmericano extends Coffee{ @Override public void brewing() { System.out.print("KenyaAmericano "); } }
-
class EtiopiaAmericano
extends Coffee
public class EtiopiaAmericano extends Coffee{ @Override public void brewing() { System.out.print("EtiopiaAmericano "); } }
-
Test 클래스
public class CoffeeTest { public static void main(String[] args) { Coffee kenyaAmericano = new KenyaAmericano(); kenyaAmericano.brewing(); System.out.println(); Coffee kenyaLatte = new Latte(kenyaAmericano); kenyaLatte.brewing(); System.out.println(); Mocha kenyaMocha = new Mocha(new Latte(new KenyaAmericano())); kenyaMocha.brewing(); System.out.println(); WhippedCream etiopiaWhippedMocha = new WhippedCream(new Mocha(new Latte( new EtiopiaAmericano()))); etiopiaWhippedMocha.brewing(); System.out.println(); } }
-
결과값
KenyaAmericano KenyaAmericano Adding Milk KenyaAmericano Adding Milk Adding Mocha Syrup EtiopiaAmericano Adding Milk Adding Mocha Syrup Adding WhippedCream