2014년 3월 9일 일요일

객체의 직렬화

객체의 직렬화는 메모리의 정보를 통신으로 전달하거나, 하드에 카피할 때 주로 쓰인다.
자바가 메모리에 실릴때는, 스태틱영역(1), 클래스로더영역(2), 스택영역(3), 힙영역(4)에 실리게 되어 있단다.
요런 클래스를 만들어 보자.
public class TestSeriallzation {
public static String str1;
public String str2;
public String str3 = "abcd";
}
이렇게 만든 녀석을 다른곳에서 생성을 한다.

TestSeriallzation testMemory = new TestSeriallzation();

이렇게 생성을 할 경우 각각의 정보는 어디에 들어갈까?
str1은 스태틱영역에 기록될 것이고, 기본적인 클래스 정보는 클래스로더영역에 기록될 것이다. 그리고, 이들의 참조값(각각의 멤버변수가 가지고 있는 주소정보)는 스택영역에, 멤버변수가 가리키고 있는 주소에 저장되어 있는 정보("abcd"등)은 힙영역에 저장될 것이다.

기본적으로 우리가 클래스를 생성할때 자바머신은 위와 같은 형태로 메모리에 적재하는 일을 담당한다. 그럼 통신을 할때는? 하드디스크에 기록하고 싶을때는?

하드디스크나, 네트웍 스트림의 경우는 위와같이 정보 기록을 분류하여 기록할수 있게 되어 있지 않다. 따라서 위와같이 분류되어 있는 정보를 하나의 정보로 만들어 보내주어야 할 필요가 있는 것이다. 이럴때 사용하는 것이 직렬화(Serialization)이다.

직렬화란 객체를 바이트로 저장하는 기술이다. 즉 위와 같이 가상머신에 존재하는 특정 객체의 메모리를 바이트의 형태로 변환한다는 거다.
1010101010101010101010101010101010101010101010101010101010101010101
요론 형태로 바꾸어서, 정보를 보내주는 거다.

자바에서의 직렬화를 하는 방법이란.. 의외로 간단하다.
자바에서는 직렬화를 위한 인터페이스를 두개 준비하고 있는데, 우리는 이것은 클래스 선언부에서 구현만 해 줌으로써, 직렬화를 할 수 있게 된다.
public class TestBean1 implements Serialzable {
}
public class TestBean2 implements Externalizable {
    public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
    }
    public void writeExternal(ObjectOutput oo) throws IOException {
    }
}
구체적인 구현방법은 직접 해보길 바란다. 모 별건 없다.
다만 Externalizable를 구현할 때는 위와 같이 두개의 메서드를 구현해 주어야 한다는 것 뿐이다.

이렇게 직렬화 된 클래스는 나중에 해당형으로 캐스팅 하여 다시 쓸수 있는데 그 좋은 예가 여기 있다.
클래스1 : ExternalObject
package test;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class ExternalObject implements Externalizable{

 private int id1;
 private int id2;
 private String name1;
 private String name2;
 private float height1;
 private float height2;
 public ExternalObject(){};
 public ExternalObject(int id1,int id2,String name1,String name2,float height1,float height2){
  this.id1 = id1;
  this.id2 = id2;
  this.name1 = name1;
  this.name2 = name2;
  this.height1 = height1;
  this.height2 = height2;
 }
 public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
  // TODO 자동 생성된 메소드 스텁
  System.out.println("readExternal()メソッドの呼出");
  id2 = oi.readInt();
  id1 = oi.readInt();
  name2 = (String)oi.readObject();
  name1 = (String)oi.readObject();
  height2 = oi.readFloat();
  height1 = oi.readFloat();
 }
 public void writeExternal(ObjectOutput oo) throws IOException {
  // TODO 자동 생성된 메소드 스텁
  System.out.println("writeExternal()メソッドの呼出");
  oo.writeInt(id1);
  oo.writeInt(id2);
  oo.writeObject(name1);
  oo.writeObject(name2);
  oo.writeFloat(height1);
  oo.writeFloat(height2);
 }

 public String toString(){
  return id1 + ":" + id2 + ":" + name1 + ":" + name2 + ":" + height1 + ":" + height2;
 }
}

클래스2 : ExternalObjectMain
package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ExternalObjectMain {
 public static void main(String [] args)
  throws IOException, ClassNotFoundException{
  // 파일스트림을 형성한다. external.dat라는 파일로부터 정보를 얻을 객체를 형성한다.
  FileOutputStream fos = new FileOutputStream("external.dat");
  // 이 파일 오브젝트로부터 오브젝트스트림 정보를 얻는다.
  ObjectOutputStream oos = new ObjectOutputStream(fos);
  // 각각의 생성자를 생성하며 클래스의 멤버변수에 내가 원하는 정보를 입력해 놓는다.
  ExternalObject so1 = new ExternalObject(1,2,"aaa","bbb",0.1f,0.2f);
  ExternalObject so2 = new ExternalObject(3,4,"ccc","ddd",0.3f,0.4f);
  ExternalObject so3 = new ExternalObject(5,6,"eee","fff",0.5f,0.6f);
  // ExternalObject클래스로부터 생성된 세개의 객체를 오브젝트 스트림에 저장한다.
  oos.writeObject(so1);
  oos.writeObject(so2);
  oos.writeObject(so3);
  // 오브젝트 스트림을 닫는다.
  oos.close();

  // 파일로부터 스트림객체를 얻는다.
  FileInputStream fis = new FileInputStream("external.dat");
  // 파일스트림객체로부터 오브젝트스트림 객체를 얻는다.
  ObjectInputStream ois = new ObjectInputStream(fis);
  // 오브젝트 스트림으로 부터 각각의 객체를 형성한다.
  ExternalObject rso1 = (ExternalObject)ois.readObject();
  ExternalObject rso2 = (ExternalObject)ois.readObject();
  ExternalObject rso3 = (ExternalObject)ois.readObject();
  // 재정의된 toString를 실행해보자.
  System.out.println(rso1.toString());
  System.out.println(rso2.toString());
  System.out.println(rso3.toString());
 }
}
결과 :
writeExternal()メソッドの呼出
writeExternal()メソッドの呼出
writeExternal()メソッドの呼出
readExternal()メソッドの呼出
readExternal()メソッドの呼出
readExternal()メソッドの呼出
2:1:bbb:aaa:0.2:0.1
4:3:ddd:ccc:0.4:0.3
6:5:fff:eee:0.6:0.5

위의 결과를 보자. 내가 원하는 대로 참 잘 나왔다. 이 결과에서 주목해야 할 것은....
바로 오브젝트 스트림에 저장할때나 읽어올때, 오브젝트 스트림은 그 안에서 클래스의 정보와 멤버변수의 정보를 그 어떠한 방법으로 구분하고 있다는 것이다.

나는 클래스의 객체를 저장할때(1)나 멤버변수를 저장할때(2) 다음과 같이 했다.
(1) oos.writeObject(so1);
     oos.writeObject(so2);
     oos.writeObject(so3);

(2) oo.writeInt(id1);
     oo.writeInt(id2);
     oo.writeObject(name1);
     oo.writeObject(name2);
     oo.writeFloat(height1);
     oo.writeFloat(height2);

그리고 읽어올때는 다음과 같이 했다.
(1) ExternalObject rso1 = (ExternalObject)ois.readObject();
     ExternalObject rso2 = (ExternalObject)ois.readObject();
     ExternalObject rso3 = (ExternalObject)ois.readObject();

(2)  id2 = oi.readInt();
      id1 = oi.readInt();
      name2 = (String)oi.readObject();
      name1 = (String)oi.readObject();
      height2 = oi.readFloat();
      height1 = oi.readFloat();

위에서 보면 쓸때나 읽어올때 사용자가 임의로 키값을 넣어주지는 않고 있다. 하지만 읽어 올때는 이 키값 없이도 내가 저장했던 대로의 값을 읽어올수 있는 것을 확인 할 수 있다.
확실하지는 않다. 그냥 느낌이지만, 오브젝트 스트림내에서 클래스의 정보 및 멤버변수의 정보까지도 모두 직렬화 해 정보를 저장하도록 되어 있다고 느낄수 밖에...
즉, 앞에서 언급한 네가지의 메모리 영역중에 스태틱 영역을 제외한 세가지 영역의 정보는 모두 직렬화되어 저장된다는 것이다.

댓글 없음:

댓글 쓰기