[Day +22]입출력(IO) / 기본 타입 입출력 보조 스트림, 객체 입출력 보조 스트림, 직렬화, 역직렬화
210722 목
입출력(IO)
1. 보조 스트림
1) 기본 타입 입출력 보조 스트림
기본 자료형 별 데이터 읽고 쓰기가 가능하도록 기능 제공
단, 입력된 자료형의 순서와 출력될 자료형의 순서 일치
package com.kh.chap04_assist.part03_data.model.dao;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class DataTest {
//DataInputStream
//DataOutputStream
// => 데이터를 8가지 기본자료형과 String 참조 자료형 단위로 읽고 쓸 수 있는
// '바이트'기반 '보조' 스트림
private String fileName = "member.txt";
private String fileName2 = "memberList.dat";
public void fileSave() {
try(DataOutputStream dout = new DataOutputStream(new FileOutputStream(fileName))){
//Data보조 스트림을 사용함으로써 write+자료형 메소드 기능 추가
dout.writeUTF("부승관");
dout.writeInt(20);
dout.writeChar('F');
dout.writeDouble(173.5);
//int는 4byte, double은 8byte의 크기로 저장하기 때문에
//txt(문자 기반 파일)을 통해 해석하면 우리 눈에 보이는 모습으로 확인 불가
//따라서 char인 F만 해석 가능
// => 타입에 맞게 읽어와야 데이터 확인 가능
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void fileOpen() {
try(DataInputStream din = new DataInputStream(new FileInputStream(fileName))){
String name = din.readUTF();
int age = din.readInt();
char gender = din.readChar();
double height = din.readDouble();
System.out.println(name + "/" + age + "/" + gender + "/" + height);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//답 : 우별림/20/F/171.5
}
public void fileSave2() {
Scanner sc = new Scanner(System.in);
try(DataOutputStream dout = new DataOutputStream(new FileOutputStream(fileName2))){
while(true) {
System.out.print("이름 입력 : ");
String name = sc.next();
System.out.print("나이 입력 : ");
int age = sc.nextInt();
System.out.print("성별 입력 : ");
char gender = sc.next().charAt(0);
System.out.print("키 입력 : ");
double height = sc.nextDouble();
dout.writeUTF(name);
dout.writeInt(age);
dout.writeChar(gender);
dout.writeDouble(height);
System.out.println("입력을 끝내시겠습니까?(y/n) : ");
char ch = sc.next().toUpperCase().charAt(0);
if(ch == 'Y') break;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void fileOpen2() {
try(DataInputStream din = new DataInputStream(new FileInputStream(fileName2))){
while(true) {
System.out.println(din.readUTF() + "," + din.readInt() + ","
+ din.readChar() + "," + din.readDouble());}
} catch(EOFException e) {
//실제 파일에 몇 명의 정보가 들어 있는지 알 수 없으므로
//무한 루프로 출력하면서 파일의 끝을 만나 EOFException이 발생하면
//catch 블럭에서 잡아줌
System.out.println("파일을 다 읽어 왔습니다.");
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.kh.chap04_assist.part03_data.run;
import com.kh.chap04_assist.part03_data.model.dao.DataTest;
public class Run {
public static void main(String[] args) {
DataTest dt = new DataTest();
dt.fileSave2();
dt.fileOpen2();
}
}
2) 객체 입출력 보조 스트림
: 객체를 파일 또는 네트워크로 입출력 할 수 있는 기능 제공
-단, 객체는 문자가 아니므로 바이트 기반 스트림으로 데이터를 변경해주는 직렬화 필수
-ObjectOutStream과 ObjectInputStream 보조 스트림을 연결하면 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다.
(1) 직렬화와 역직렬화
1> 직렬화
: 객체를 바이트 배열로 만드는 것
-Serializable 인터페이스를 implements하여 구현
-객체 직렬화 시 private 필드를 포함한 모든 필드를 바이트로 변환하지만 transient키워드를 사용한 필드는 직렬화에서 제외
자바는 모든 객체를 직렬화하지 않는다.
java.io.Serializable 인터페이스를 구현한 객체만 직렬화한다.
Serializble 인터 페이스
: 메소드 선언이 없는 인터페이스
-객체를 파일로 저장하거나 네트워크를 전송할 목적이면 클래스를 선언할 때 implements Serializable을 추가해야 한다. (이는 개발자가 JVM에게 직렬화해도 좋다고 승인하는 역할을 하는 것이다)
2> 역직렬화(Deserialization)
: 바이트 배열을 다시 객체로 복원하는 것
-직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스 사용
-단, 클래스 이름이 같더라도 클래스 내용이 변경된 경우 역직렬화 실패
3> serialVersionUID 필드
직렬화한 클래스와 같은 클래스임을 알려주는 식별자 역할로 컴파일 시 JVM이 자동으로 serialViersionUID 정적 필드를 추가해줘 별로도 작성하지 않아도 오류는 나지 않지만 자동 생성 시 역직렬화에서 예상하지 못한 InvalidClassException을 유발할 수 있어 명시 권장 private static final long serialVersionUID = -6423919775137290062L;
package com.kh.chap04_assist.part04_object.model.dao;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.kh.chap04_assist.part04_object.model.vo.Phone;
import com.kh.chap04_assist.part04_object.model.vo.Student;
public class ObjectDao {
private Student[] stList = new Student[10];
{
// 샘플 데이터
stList[0] = new Student("우별림", 20, 'F', new Phone("삼성", 990000));
stList[1] = new Student("김철수", 21, 'M', new Phone("LG", 880000));
}
public void fileSave() {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("studentList.dat"))) {
// 1. Student 한 명씩 저장
for(int i = 0; i < stList.length; i++) {
if(stList[i] == null) break; // 데이터가 있는 인덱스만 write 하려면
oos.writeObject(stList[i]);
}
// 2. Student 배열 채로 저장
// oos.writeObject(stList);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void fileOpen() {
Student[] list = null;
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("studentList.dat"))){
// 1. Student 한명씩 읽어오기
list = new Student[10];
for(int i = 0; i < list.length; i++) {
list[i] = (Student) ois.readObject();
}
// 2. Student 배열채로 읽어오기
// list = (Student[]) ois.readObject();
} catch(EOFException e) {
System.out.println("studentList.dat 파일 읽기 완료");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 잘 읽어왔는지 출력 확인
for(Student st : list) {
System.out.println(st);
}
}
}
package com.kh.chap04_assist.part04_object.model.vo;
import java.io.Serializable;
public class Phone implements Serializable {
//price => productID로 버전 변경 테스트 해보기
//1. serialVersionUId 필드 명시적으로 선언하지 않고 변경
// => java.io.InvalidClassException 발생
//출력 시 JVm이 자동으로 생성한 SerialVersionUID(직렬화시 사용)와
//입력시 자동으로 생성한 SerialVersionUID 불일치로 역직렬화 실패함
//2. SerialVersionUId 필드 명시적으로 선언하고 변경
//=> 직렬화 시 저장된 SerialBersionUID와 역직렬화 시 확인한 SerialVersionUID가 동일
//해서 역직렬화 성공함. 단 새로 생긴 필드는 초기값만 가지고 있음.
/**
*
*/
private static final long serialVersionUID = 5360818841505606660L;
private String brand;
private int price;
public Phone() {}
public Phone(String brand, int price) {
super();
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public int getPrice() {
return price;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Phone [brand=" + brand + ", price=" + price + "]";
}
}
package com.kh.chap04_assist.part04_object.model.dao;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.kh.chap04_assist.part04_object.model.vo.Phone;
public class SimpleDao {
//ObjectInputStream
//ObjectOutputStream
//자바 프로그램(객체 단위) --> 직 렬 화 -- 파일(바이트 단위)
//파일(바이트 단위) -- 역 직 렬 화 --> 자바 프로그램(객체 단위)
public void fileSave() {
Phone ph = new Phone("삼성", 900000);
Phone ph2 = new Phone("LG", 880000);
try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("phone.dat"))){
oos.writeObject(ph);
oos.writeObject(ph2);
//java.io.NotSerializableException => Phone 클래스 직렬화 불가 오류
//Phone implements Serializable 추가하기
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void fileOpen() {
try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("phone.dat"))){
//System.out.println(ois.readObject());
//System.out.println(ois.readObject());
//Phone타입 변수에 담아서 출력
//=> readObject 메소드의 리턴 타입은 Object이므로 Phone 타입으로 다운 캐스팅 필요
Phone p1 = (Phone)ois.readObject();
Phone p2 = (Phone)ois.readObject();
System.out.println(p1);
System.out.println(p2);
//더 읽으려고 한다면?
// => java.io.EOFException발생
Phone p3 = (Phone)ois.readObject();
System.out.println(p3);
}catch (EOFException e) {
System.out.println("파일을 다 읽었습니다.");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
package com.kh.chap04_assist.part04_object.model.vo;
import java.io.Serializable;
public class Student implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5765229820101083029L;
private String name;
private int age;
private char gender;
private Phone ph;
public Student() {}
public Student(String name, int age, char gender, Phone ph) {
super();
this.name = name;
this.age = age;
this.gender = gender;
this.ph = ph;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public char getGender() {
return gender;
}
public Phone getPh() {
return ph;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(char gender) {
this.gender = gender;
}
public void setPh(Phone ph) {
this.ph = ph;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", gender=" + gender + ", ph=" + ph + "]";
}
}
package com.kh.chap04_assist.part04_object.run;
import com.kh.chap04_assist.part04_object.model.dao.ObjectDao;
import com.kh.chap04_assist.part04_object.model.dao.SimpleDao;
public class Run {
public static void main(String[] args) {
SimpleDao sd = new SimpleDao();
sd.fileSave();
sd.fileOpen();
ObjectDao od = new ObjectDao();
od.fileSave();
od.fileOpen();
}
}