Java 객체 정렬화 및 반정렬화 자세히 보기

이전 글에서 우리는 바이트 흐름 문자 흐름의 사용에 대해 소개한 적이 있다. 당시 우리는 하나의 대상을 흐름으로 출력하는 작업에 대해 DataOutputStream 흐름을 사용하여 이 대상의 모든 속성 값을 흐름으로 하나씩 출력했다. 읽을 때 상반된다.우리가 보기에 이런 행위는 정말 번거롭다. 특히 이 대상에서 속성 값이 매우 많을 때.이를 바탕으로 자바에서 대상의 서열화 메커니즘은 이런 조작을 잘 해결할 수 있다.다음은 Java 객체의 서열화에 대한 간단한 설명입니다.
  • 간결한 코드 구현
  • 서열화 실현의 기본 알고리즘
  • 두 가지 특수한 상황
  • 사용자 정의 서열화 메커니즘
  • 서열화된 버전 제어
  • 1. 간결한 코드 구현
    대상의 서열화된 사용 방법을 소개하기 전에, 먼저 우리가 이전에 어떻게 대상 유형의 데이터를 저장했는지 봅시다.
    
    // Student 
    public class Student {
    
     private String name;
     private int age;
    
     public Student(){}
     public Student(String name,int age){
     this.name = name;
     this.age=age;
     }
    
     public void setName(String name){
     this.name = name;
     }
     public void setAge(int age){
     this.age = age;
     }
     public String getName(){
     return this.name;
     }
     public int getAge(){
     return this.age;
     }
     // toString
     @Override
     public String toString(){
     return ("my name is:"+this.name+" age is:"+this.age);
     }
    }
    
    
    //main 
    public static void main(String[] args) throws IOException{
    
     DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
     Student stuW = new Student("walker",21);
     // 
     dot.writeUTF(stuW.getName());
     dot.writeInt(stuW.getAge());
     dot.close();
    
     // 
     DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
     Student stuR = new Student();
     stuR.setName(din.readUTF());
     stuR.setAge(din.readInt());
     din.close();
    
     System.out.println(stuR);
     }
    
    
    출력 결과: my name is:walker age is:21
    분명히 이런 코드 작성은 번거롭다. 다음은 서열화를 사용하여 저장 대상의 정보를 어떻게 완성하는지 보자.
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
     Student stuW = new Student("walker",21);
     oos.writeObject(stuW);
     oos.close();
    
     // 
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
     Student stuR = (Student)ois.readObject();
     System.out.println(stuR);
     }
    
    파일을 쓸 때 한 문장만 사용하면 writeObject이고, 읽을 때도 한 문장만 사용하면 readObject입니다.그리고 Student의 set, get 방법도 사용할 수 없습니다.깔끔하지 않아요?다음은 실현 디테일을 소개한다.
    2. 서열화를 실현하는 기본 알고리즘
    이런 메커니즘에서 모든 대상은 유일한 서열 번호에 대응하고 모든 대상은 저장될 때도 이 서열 번호에 따라 서로 다른 대상에 대응한다. 대상 서열화는 모든 대상의 서열 번호를 이용하여 저장하고 읽는 것을 말한다.먼저 쓰기 대상을 흐름으로 예를 들면, 모든 대상에 대해 처음 만났을 때 이 대상의 기본 정보를 흐름에 저장하고, 현재 만났던 대상이 저장되었다면 이 정보를 다시 저장하지 않고 이 대상의 시퀀스 번호를 기록한다. (데이터가 중복 저장될 필요가 없기 때문이다.)읽는 경우, 흐름에서 만나는 모든 대상은 처음 만나면 직접 출력하고, 읽은 것이 어떤 대상의 시퀀스 번호이면 연관된 대상을 찾아 출력합니다.
    몇 가지를 설명하자면, 하나의 대상이 서열화되려면 반드시 인터페이스java를 실현해야 한다.io.Serializable;,이것은 표기 인터페이스로 어떠한 방법도 실현할 필요가 없다.그리고 ObjectOutputStream 흐름은 객체 정보를 바이트로 바꿀 수 있는 흐름입니다. 구조 함수는 다음과 같습니다.
    
    public ObjectOutputStream(OutputStream out)
    즉, 모든 바이트 흐름은 매개 변수로 전송되고 모든 바이트 조작을 겸용할 수 있다.이 흐름에서 writeObject와readObject 방법을 정의하여 서열화 대상과 반서열화 대상을 실현하였다.물론 우리도 클래스에서 이 두 가지 방법을 실현하여 사용자 정의 서열화 메커니즘을 실현할 수 있다. 구체적인 뒷글은 소개한다.여기에서 우리는 전체 서열화 메커니즘을 이해하기만 하면 모든 대상 데이터는 한 부만 저장하고 같은 대상이 다시 나타나면 대응하는 서열 번호만 저장할 수 있다.다음은 두 가지 특수한 상황을 통해 그의 이 기본 알고리즘을 직관적으로 느낄 수 있다.
    3. 두 가지 특수한 실례
    첫 번째 인스턴스 보기:
    
    public class Student implements Serializable {
    
     String name;
     int age;
     Teacher t; // 
    
     public Student(){}
     public Student(String name,int age,Teacher t){
     this.name = name;
     this.age=age;
     this.t = t;
     }
    
     public void setName(String name){this.name = name;}
     public void setAge(int age){this.age = age;}
     public void setT(Teacher t){this.t = t;}
     public String getName(){return this.name;}
     public int getAge(){return this.age;}
     public Teacher getT(){return this.t;}
    }
    
    public class Teacher implements Serializable {
     String name;
    
     public Teacher(String name){
     this.name = name;
     }
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
     Teacher t = new Teacher("li");
     Student stu1 = new Student("walker",21,t);
     Student stu2 = new Student("yam",22,t);
     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
     oos.writeObject(stu1);
     oos.writeObject(stu2);
    
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
     Student stuR1 = (Student)ois.readObject();
     Student stuR2 = (Student)ois.readObject();
    
     if (stuR1.getT() == stuR2.getT())
      System.out.println(" ");
     }
    
    
    결과는 명백히 알 수 있다. 같은 대상을 출력했다.우리는main 함수에서 두 개의 학생 형식 대상을 정의했는데, 그들이 모두 인용한 같은teacher 대상은 내부에 있다.서열화가 끝난 후에 두 개의 대상을 반서열화하여 그들 내부의teacher 대상이 같은 실례인지 비교함으로써 알 수 있듯이 첫 번째 학생 대상을 서열화할 때 t는 흐름에 기록되었지만 두 번째 학생 대상의teacher 대상 실례를 만났을 때 앞에 이미 쓴 것을 발견하여 흐름에 쓰지 않고 대응하는 서열 번호만 인용으로 저장했다.물론 반서열화할 때 원리는 유사하다.이것은 우리가 위에서 소개한 기본 알고리즘과 같다.
    두 번째 특수 인스턴스는 다음과 같습니다.
    
    public class Student implements Serializable {
    
     String name;
     Teacher t;
    
    }
    
    public class Teacher implements Serializable {
     String name;
     Student stu;
    
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
     Teacher t = new Teacher();
     Student s =new Student();
     t.name = "walker";
     t.stu = s;
     s.name = "yam";
     s.t = t;
    
     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
     oos.writeObject(t);
     oos.writeObject(s);
     oos.close();
    
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
     Teacher tR = (Teacher)ois.readObject();
     Student sR = (Student)ois.readObject();
     if(tR == sR.t && sR == tR.stu)System.out.println("ok");
    
     }
    
    
    출력의 결과는 ok입니다. 이 예는 순환 인용이라고 할 수 있습니다.결과에서 알 수 있듯이 서열화 전에 두 대상이 존재했던 상호적인 인용 관계는 서열화를 거친 후에 양자 간의 이런 인용 관계는 여전히 존재한다.사실 우리가 전에 소개한 판단 알고리즘에 따르면 먼저 우리는 교사 대상을 서열화했다. 왜냐하면 그는 내부에서 학생의 대상을 인용했기 때문에 둘 다 처음 만났기 때문에 둘을 흐름으로 서열화한 다음에 우리는 학생 대상을 서열화했다. 이 대상과 내부의 교사 대상이 모두 서열화된 것을 발견하고 해당하는 서열 번호만 저장했다.읽을 때 시퀀스 번호에 따라 대상을 복원합니다.
    4. 사용자 정의 서열화 메커니즘
    종합적으로 말하자면, 우리는 이미 기본적인 서열화와 반서열화 지식을 소개했다.그러나 왕왕 우리는 특수한 요구가 있다. 이런 기본적인 서열화 메커니즘은 이미 완선되었지만 어떤 때는 우리의 수요를 만족시키지 못한다.그래서 우리는 어떻게 사용자 정의 서열화 메커니즘을 만드는지 봅시다.사용자 정의 서열화 메커니즘에서 우리는 키워드를 사용할 것이다. 이것은 우리가 이전에 원본 코드를 볼 때 자주 만났던transient이다.필드를transient라고 설명하는 것은 기본적인 서열화 메커니즘을 알려주는 것과 같습니다. 이 필드는 흐름에 쓰지 마세요. 제가 알아서 처리할게요.
    
    public class Student implements Serializable {
    
     String name;
     transient int age;
    
     public String toString(){
     return this.name + ":" + this.age;
     }
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
     Student stu = new Student();
     stu.name = "walker";stu.age = 21;
     oos.writeObject(stu);
    
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
     Student stuR = (Student)ois.readObject();
    
     System.out.println(stuR);
     }
    
    
    출력 결과:walker:0
    우리는age 필드에 초기값을 부여하지 않았습니까? 어떻게 0입니까?위에서 말한 바와 같이transient에 의해 수식된 필드는 흐름에 기록되지 않으며, 자연적으로 읽으면 값이 없습니다. 기본값은 0입니다.다음은 우리가 어떻게 이 age를 서열화하는지 봅시다.
    
    // student ,main , 
    public class Student implements Serializable {
    
     String name;
     transient int age;
    
     private void writeObject(ObjectOutputStream oos) throws IOException {
     oos.defaultWriteObject();
    
     oos.writeInt(25);
     }
    
     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
     ois.defaultReadObject();
    
     age = ois.readInt();
     }
    
     public String toString(){
     return this.name + ":" + this.age;
     }
    }
    
    
    출력 결과:walker:25
    결과는 내가 초기화한 21도 0도 아니라 우리가 writeObject 방법에서 쓴 25이다.이제 우리는 매 단계의 의미를 하나하나 살펴보자.우선, 사용자 정의 서열화를 실현하려면 이 대상이 정의한 클래스에서 두 가지 방법, writeObject와readObject를 실현해야 한다. 그리고 형식은 반드시 위에 붙인 것과 같아야 한다. 필자는 방법을 바꾸어 수식자를 시도했지만 결과적으로 서열화에 성공하지 못했다.이것은 자바가 반사 메커니즘을 사용하여 이 대상이 있는 클래스에서 이 두 가지 방법이 실현되었는지 확인하고, 없으면 기본적인 Object Output Stream에서 이 방법으로 모든 필드를 서열화하고, 있으면 당신이 실현한 이 방법을 실행하기 때문이다.
    다음은 이 두 가지 방법이 실현하는 세부 사항을 보고 writeObject 방법을 보십시오. 매개 변수는 Object Output Stream 형식입니다. 이것은main 방법에서 정의한 Object Output Stream 대상입니다. 그렇지 않으면 대상을 그곳에 써야 한다는 것을 어떻게 알 수 있습니까?첫 번째 줄에서 저희가 호출한 것은oos입니다.defaultWriteObject();이 방법이 실현하는 기능은 현재 대상에transient로 장식되지 않은 모든 필드를 흐름에 기록하는 것입니다. 두 번째 문장은 writeInt 방법을 사용하여 age의 값을 흐름에 기록하는 것입니다.읽는 방법은 유사합니다. 여기에서 더 이상 설명하지 않겠습니다.
    5. 버전 제어
    마지막으로 서열화 과정의 버전 제어 문제를 살펴보자.우리가 하나의 대상을 흐름으로 서열화한 후에 이 대상에 대응하는 클래스의 구조가 바뀌었다. 만약에 우리가 흐름에서 이전에 저장한 대상을 다시 읽으면 무슨 일이 일어날까?이것은 상황별로 말하자면, 원류의 필드가 삭제되면, 흐름에서 출력되는 대응하는 필드는 무시됩니다.클래스에 필드가 추가되면 추가된 필드의 값이 기본값입니다.필드의 형식이 바뀌면 이상을 던집니다.자바에는 각 클래스마다 버전 번호를 기록하는 변수가 있습니다. static final serivalVersionUID = 1156165165L입니다. 이 값은 어떤 클래스에도 대응하지 않는 것을 보여 주는 데만 사용됩니다.이 버전 번호는 이 클래스의 필드 등 일부 속성 정보에 따라 계산된 것으로 유일성이 비교적 높다.읽을 때마다 이전과 현재의 버전 번호를 비교하여 버전이 일치하지 않는지 확인하고, 버전이 일치하지 않으면 상술한 상황에 따라 각각 처리한다.
    대상의 서열화는 다 썼습니다. 만약 어떤 내용이 타당하지 않은 부분이 있다면 여러분이 지적하시기 바랍니다!
    이상은 본문의 전체 내용입니다. 여러분의 학습에 도움이 되고 저희를 많이 응원해 주십시오.

    좋은 웹페이지 즐겨찾기