웹개발 수업/JAVA

[Day +22]입출력(IO) / 기본 타입 입출력 보조 스트림, 객체 입출력 보조 스트림, 직렬화, 역직렬화

Chole Woo 2021. 7. 22. 19:18
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();
	
	}

}