>  기사  >  Java  >  Java 객체의 직렬화 및 역직렬화에 대해 이야기

Java 객체의 직렬화 및 역직렬화에 대해 이야기

青灯夜游
青灯夜游앞으로
2020-07-03 10:03:592429검색

Java 객체의 직렬화 및 역직렬화에 대해 이야기

1. 직렬화와 역직렬화의 개념

객체를 바이트 시퀀스로 변환하는 과정을 객체 직렬화라고 합니다.

바이트 시퀀스를 객체로 복원하는 프로세스를 객체 역직렬화라고 합니다.

객체 직렬화에는 두 가지 주요 용도가 있습니다.

1) 객체의 바이트 시퀀스를 일반적으로 파일에 저장되는 하드 디스크에 영구적으로 저장합니다.

2) 네트워크 시퀀스를 통해 객체의 바이트를 전송합니다. .

많은 응용 프로그램에서 특정 객체는 메모리 공간을 떠나 물리적 하드 디스크에 저장하여 장기간 보관할 수 있도록 직렬화해야 합니다. 예를 들어, 가장 일반적인 것은 웹 서버의 세션 개체입니다. 100,000명의 사용자가 동시에 액세스하면 100,000개의 세션 개체가 있을 수 있으며 이는 메모리에 너무 많을 수 있으므로 웹 컨테이너는 일부 세션을 하드에 직렬화합니다. disk를 먼저 사용하는 식으로 사용하려면 하드디스크에 저장된 개체를 메모리에 복원해야 합니다.

두 프로세스가 원격으로 통신할 때 서로 다양한 유형의 데이터를 보낼 수 있습니다. 어떤 유형의 데이터이든 바이너리 시퀀스 형태로 네트워크를 통해 전송됩니다. 송신자는 이 Java 객체를 네트워크를 통해 전송하기 전에 바이트 시퀀스로 변환해야 합니다. 수신자는 바이트 시퀀스를 Java 객체로 복원해야 합니다.

2. JDK 클래스 라이브러리의 직렬화 API

java.io.ObjectOutputStream은 객체 출력 스트림을 나타냅니다. 해당 writeObject(Object obj) 메소드는 매개변수로 지정된 obj 객체를 직렬화하고 A 시퀀스를 가져올 수 있습니다. 바이트는 대상 출력 스트림에 기록됩니다.
 java.io.ObjectInputStream은 객체 입력 스트림을 나타냅니다. readObject() 메서드는 소스 입력 스트림에서 일련의 바이트를 읽고 이를 객체로 역직렬화한 후 반환합니다.
직렬화 가능 및 외부화 가능 인터페이스를 구현하는 클래스의 객체만 직렬화할 수 있습니다. 외부화 가능 인터페이스는 다음에서 상속됩니다. 직렬화 가능 인터페이스, 외부화 가능 인터페이스를 구현하는 클래스는 자체적으로 직렬화 동작을 완전히 제어하는 ​​반면, 직렬화 가능 인터페이스만 구현하는 클래스는 기본 직렬화 방법을 사용합니다.
 객체 직렬화에는 다음 단계가 포함됩니다.
 1) 파일 출력 스트림과 같은 다른 유형의 대상 출력 스트림을 래핑할 수 있는 객체 출력 스트림을 생성합니다.
 2) 객체의 writeObject() 메서드를 통해 객체를 씁니다. 출력 스트림.

 객체 역직렬화 단계는 다음과 같습니다.
 1) 파일 입력 스트림과 같은 다른 유형의 소스 입력 스트림을 래핑할 수 있는 객체 입력 스트림을 생성합니다.
 2) readObject() 메서드를 통해 객체를 읽습니다. 객체 입력 스트림의 .

객체 직렬화 및 역직렬화 예:

Person 클래스 정의 및 직렬화 가능 인터페이스 구현

import java.io.Serializable;

/**
 * <p>ClassName: Person<p>
 * <p>Description:测试对象序列化和反序列化<p>
 * @author xudp
 * @version 1.0 V
 * @createTime 2014-6-9 下午02:33:25
 */
public class Person implements Serializable {

    /**
     * 序列化ID
     */
    private static final long serialVersionUID = -5809782578272943999L;
    private int age;
    private String name;
    private String sex;

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

  Person 클래스 객체의 직렬화 및 역직렬화

import java.io.File;
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 java.text.MessageFormat;

/**
 * <p>ClassName: TestObjSerializeAndDeserialize<p>
 * <p>Description: 测试对象的序列化和反序列<p>
 * @author xudp
 * @version 1.0 V
 * @createTime 2014-6-9 下午03:17:25
 */
public class TestObjSerializeAndDeserialize {

    public static void main(String[] args) throws Exception {
        SerializePerson();//序列化Person对象
        Person p = DeserializePerson();//反序列Perons对象
        System.out.println(MessageFormat.format("name={0},age={1},sex={2}",
                                                 p.getName(), p.getAge(), p.getSex()));
    }

    /**
     * MethodName: SerializePerson
     * Description: 序列化Person对象
     * @author xudp
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void SerializePerson() throws FileNotFoundException,
            IOException {
        Person person = new Person();
        person.setName("gacl");
        person.setAge(25);
        person.setSex("男");
        // ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对Person对象的序列化操作
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("E:/Person.txt")));
        oo.writeObject(person);
        System.out.println("Person对象序列化成功!");
        oo.close();
    }

    /**
     * MethodName: DeserializePerson
     * Description: 反序列Perons对象
     * @author xudp
     * @return
     * @throws Exception
     * @throws IOException
     */
    private static Person DeserializePerson() throws Exception, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("E:/Person.txt")));
        Person person = (Person) ois.readObject();
        System.out.println("Person对象反序列化成功!");
        return person;
    }

}

코드 실행 결과는 다음과 같습니다.

Java 객체의 직렬화 및 역직렬화에 대해 이야기

Person을 성공적으로 직렬화한 후 E 드라이브에 Person.txt 파일이 생성되고, Person을 역직렬화하면 E

3 드라이브에서 Person.txt를 읽은 후 Person 개체가 생성됩니다. serialVersionUID

serialVersionUID: 말 그대로 직렬화 버전 번호를 의미합니다. 직렬화 가능 인터페이스를 구현하는 모든 클래스에는 직렬화 가능 버전 식별자를 나타내는 정적 변수가 있습니다.

private static final long serialVersionUID

직렬화 가능 인터페이스를 구현하는 클래스가 클래스에 serialVersionUID를 추가하지 않으면 다음과 같은 경고 메시지가 나타납니다.

Java 객체의 직렬화 및 역직렬화에 대해 이야기

마우스로 클릭하면 아래 그림과 같이 serialVersionUID 생성을 위한 대화 상자가 나타납니다.

Java 객체의 직렬화 및 역직렬화에 대해 이야기

serialVersionUID를 생성하는 방법에는 두 가지가 있습니다.

사용 이 메소드로 생성된 serialVersionUID는 1L입니다. 예:

private static final long serialVersionUID = 1L;

이 메소드로 생성된 serialVersionUID는 클래스 이름, 인터페이스 이름, 메소드 및 속성 등을 기반으로 생성됩니다. 예:

private static final long serialVersionUID = 4603642343377807741L;

추가한 후 그러면 아래와 같이 경고 메시지가 나타납니다.

Java 객체의 직렬화 및 역직렬화에 대해 이야기

그러면 serialVersionUID(직렬화된 버전 번호)의 용도는 무엇입니까? 다음 예를 사용하여 serialVersionUID의 역할을 설명합니다. 다음 코드에서:

import java.io.File;
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 java.io.Serializable;

public class TestSerialversionUID {

    public static void main(String[] args) throws Exception {
        SerializeCustomer();// 序列化Customer对象
        Customer customer = DeserializeCustomer();// 反序列Customer对象
        System.out.println(customer);
    }

    /**
     * MethodName: SerializeCustomer
     * Description: 序列化Customer对象
     * @author xudp
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void SerializeCustomer() throws FileNotFoundException,
            IOException {
        Customer customer = new Customer("gacl",25);
        // ObjectOutputStream 对象输出流
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("E:/Customer.txt")));
        oo.writeObject(customer);
        System.out.println("Customer对象序列化成功!");
        oo.close();
    }

    /**
     * MethodName: DeserializeCustomer
     * Description: 反序列Customer对象
     * @author xudp
     * @return
     * @throws Exception
     * @throws IOException
     */
    private static Customer DeserializeCustomer() throws Exception, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("E:/Customer.txt")));
        Customer customer = (Customer) ois.readObject();
        System.out.println("Customer对象反序列化成功!");
        return customer;
    }
}

/**
 * <p>ClassName: Customer</p><p>
 * </p><p>Description: Customer实现了Serializable接口,可以被序列化</p><p>
 * @author xudp
 * @version 1.0 V
 * @createTime 2014-6-9 下午04:20:17
 */
class Customer implements Serializable {
    //Customer类中没有定义serialVersionUID
    private String name;
    private int age;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*
     * @MethodName toString
     * @Description 重写Object类的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}</p>

실행 결과:

Java 객체의 직렬화 및 역직렬화에 대해 이야기Java 객체의 직렬화 및 역직렬화에 대해 이야기

序列化和反序列化都成功了。

下面我们修改一下Customer类,添加多一个sex属性,如下:

class Customer implements Serializable {
    //Customer类中没有定义serialVersionUID
    private String name;
    private int age;

    //新添加的sex属性
    private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /*
     * @MethodName toString
     * @Description 重写Object类的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

然后执行反序列操作,此时就会抛出如下的异常信息:

1 Exception in thread "main" java.io.InvalidClassException: Customer; 
2 local class incompatible: 
3 stream classdesc serialVersionUID = -88175599799432325, 
4 local class serialVersionUID = -5182532647273106745

意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在TestSerialversionUID例子中,没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。

下面继续修改Customer类,给Customer指定一个serialVersionUID,修改后的代码如下:

class Customer implements Serializable {
    /**
     * Customer类中定义的serialVersionUID(序列化版本号)
     */
    private static final long serialVersionUID = -5182532647273106745L;
    private String name;
    private int age;

    //新添加的sex属性
    //private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }*/

    /*
     * @MethodName toString
     * @Description 重写Object类的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

重新执行序列化操作,将Customer对象序列化到本地硬盘的Customer.txt文件存储,然后修改Customer类,添加sex属性,修改后的Customer类代码如下:

class Customer implements Serializable {
    /**
     * Customer类中定义的serialVersionUID(序列化版本号)
     */
    private static final long serialVersionUID = -5182532647273106745L;
    private String name;
    private int age;

    //新添加的sex属性
    private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /*
     * @MethodName toString
     * @Description 重写Object类的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

执行反序列操作,这次就可以反序列成功了,如下所示:

Java 객체의 직렬화 및 역직렬화에 대해 이야기

四、serialVersionUID的取值

serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。

类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值

显式地定义serialVersionUID有两种用途:

1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

推荐学习:Java视频教程

위 내용은 Java 객체의 직렬화 및 역직렬화에 대해 이야기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제