Java学习笔记6.3.3 文件操作 - 对象序列化与反序列化

Posted howard2005

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习笔记6.3.3 文件操作 - 对象序列化与反序列化相关的知识,希望对你有一定的参考价值。

文章目录

零、本讲学习目标

  1. 了解对象序列化与反序列化应用场景
  2. 掌握如何实现对象序列化与反序列化

一、对象序列化与反序列化

(一)对象序列化与反序列化概念

  • **对象的序列化(Serialization)**是指将一个Java对象转换成一个I/O流中字节序列的过程。目的是为了将对象保存到磁盘中,或允许在网络中直接传输对象。
  • 对象序列化可以使内存中的Java对象转换成与平台无关的二进制流,既可以将这种二进制流持久地保存在磁盘上,又可以通过网络将这种二进制流传输到另一个网络节点。
  • 其他程序在获得了这种二进制流后,还可以将它恢复成原来的Java对象,将I/O流中的字节序列恢复为Java对象的过程被称之为反序列化(Deserialization)

(二)对象序列化与反序列化示意图

  • 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中

(三)实际开发中序列化和反序列化的场景

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
  • 将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。

(四)实现对象序列化的两种方法

1、实现Serializable接口

  • 系统自动存储必要信息
  • Java内部支持,易实现,只需实现该接口即可,不需要其他代码支持
  • 性能较差
  • 容易实现,实际开发使用较多

2、实现Externalizable接口

  • 程序员决定存储的信息
  • 接口中提供了两个空方法,实现该接口必须为两个方法提供实现
  • 性能较好
  • 编程复杂度大

(五)对象字节输出流

1、对象字节输出流API文档

2、对象字节输出流类结构图

  • ObjectOutputStream类继承了OutputStream类,实现了ObjectOutput接口和ObjectStreamConstants接口

(六)对象字节输入流

1、对象字节输入流API文档

2、对象字节输入流类结构图

  • ObjectInputStream类继承了InputStream类,实现了ObjectInput接口和ObjectStreamConstants接口

二、对象序列化与反序列化案例演示

(一)创建学生类,实现序列化接口

  • c06.s03.p03包里创建Student类,实现Serializable接口
package c06.s03.p03;

import java.io.Serializable;

/**
 * 功能:学生类,实现序列化接口
 * 作者:华卫
 * 日期:2022年12月10日
 */
public class Student implements Serializable 
    private String name;
    private int age;

    public Student() 
    

    public Student(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

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

    public int getAge() 
        return age;
    

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

    @Override
    public String toString() 
        return "Student" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    

(二)创建测试学生类

  • c06.s03.p03包里创建TestStudent

1、测试序列化

  • 创建testWrite()方法,测试序列化
  • 使用了单元测试包JUnit4的注解符@Test,要将JUnit4添加到类路径
package c06.s03.p03;

import org.junit.Test;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/**
 * 功能:测试学生类
 * 作者:华卫
 * 日期:2022年12月10日
 */
public class TestStudent 
    /**
     * 序列化过程
     */
    @Test
    public void testWrite() throws Exception 
        // 创建学生对象
        Student student = new Student("howard", 18);
        // 创建文件字节输出流
        FileOutputStream fos = new FileOutputStream("src/c06/s03/p03/test1.txt");
        // 创建对象字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        // 写入学生对象数据
        oos.writeObject(student);
        // 关闭文件字节输出流
        fos.close();
        // 关闭对象字节输出流
        oos.close();
    

  • 运行testWrite()方法,查看结果

  • 查看生成的输出文件test1.txt

  • 说明:本来写入的对象只包含两项数据:howard18,可以看到序列化后的数据明显增多了,这是Java原生序列化的一个局限性。还有一点,看到的是乱码。

  • 修改Student类,不实现Serializable接口

  • 运行testWrite()方法就会抛出NotSerializableException异常

  • 修改Student类,实现Serializable接口

  • 序列化id: 每个对象序列化时都会生成一个序列化id,假如不手动设置,那么会自动根据当前这个类生成一个序列化id。当反序列化时,要根据序列化id来操作,如果序列化id和反序列化id不同,那么反序列化就会失败。

2、测试反序列化

  • 编写testRead()方法,测试反序列化
/**                                                                                 
 * 反序列化过程                                                                           
 */                                                                                 
@Test                                                                               
public void testRead() throws Exception                                            
    // 创建文件字节输入流                                                                    
    FileInputStream fis = new FileInputStream("src/c06/s03/p03/test1.txt");         
    // 创建对象字节输入流                                                                    
    ObjectInputStream ois = new ObjectInputStream(fis);                             
    // 读取学生对象数据                                                                     
    Student student = (Student) ois.readObject();                                   
    // 输出学生信息                                                                       
    System.out.println(student);                                                    
    // 关闭文件字节输入流                                                                    
    fis.close();                                                                    
    // 关闭对象字节输入流                                                                    
    ois.close();                                                                    
                                                                                                                                   
  • 运行testRead()方法,查看结果

  • 确实将先前序列化保存在文件中的数据读取出来,然后反序列化成Java对象输出。

3、serialVersionUID作用

  • serialVersionUID适用于Java的序列化机制。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就可以进行反序列化,否则就会出现异常。因此,为了在反序列化时确保序列化版本的兼容性,最好在每一个要序列化的类中加入private static final long serialVersionUID的变量值,具体数值可自定义(默认是1L,系统还可以根据类名、接口名、成员方法及属性等生成的一个64位的哈希字段)。这样,某个对象被序列化之后,即使它所对应的类被修改了,该对象也依然可以被正确地反序列化。
  • 修改Student类,增加一个字段gender,添加对应的getter和setter,修改toString()方法
package c06.s03.p03;

import java.io.Serializable;

/**
 * 功能:学生类,实现序列化接口
 * 作者:华卫
 * 日期:2022年12月10日
 */
public class Student implements Serializable 
    private String name;
    private int age;
    private String gender;

    public Student() 
    

    public Student(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

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

    public int getAge() 
        return age;
    

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

    public String getGender() 
        return gender;
    

    public void setGender(String gender) 
        this.gender = gender;
    

    @Override
    public String toString() 
        return "Student" +
                "name='" + name + '\\'' +
                ", age=" + age +
                ", gender='" + gender + '\\'' +
                '';
    

  • 运行TestStudent类的testRead()方法,会抛出InvalidClassException异常

  • 异常信息:java.io.InvalidClassException: c06.s03.p03.Student; local class incompatible: stream classdesc serialVersionUID = -6230387240686779692, local class serialVersionUID = 8464671208967089518

  • 序列化id:local class serialVersionUID = 8464671208967089518

  • 反序列化id:stream classdesc serialVersionUID = -6230387240686779692

  • 用错误提示信息中的反序列化id-6230387240686779692)去给Student类设置序列化id

  • 再次运行testRead()方法,查看结果

  • 修改testWrite()方法

  • 运行testWrite()方法

  • 运行testRead()方法,查看结果

  • 查看TestStudent类源代码

package c06.s03.p03;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 功能:测试学生类
 * 作者:华卫
 * 日期:2022年12月10日
 */
public class TestStudent 
    /**
     * 序列化过程
     */
    @Test
    public void testWrite() throws Exception 
        // 创建学生对象
        Student student = new Student("howard", 18);
        student.setGender("男");
        // 创建文件字节输出流
        FileOutputStream fos = new FileOutputStream("src/c06/s03/p03/test1.txt");
        // 创建对象字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        // 写入学生对象数据
        oos.writeObject(student);
        // 关闭文件字节输出流
        fos.close();
        // 关闭对象字节输出流
        oos.close();
    

    /**
     * 反序列化过程
     */
    @Test
    public void testRead() throws Exception 
        // 创建文件字节输入流
        FileInputStream fis = new FileInputStream("src/c06/s03/p03/test1.txt");
        // 创建对象字节输入流
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 读取学生对象数据
        Student student = (Student) ois.readObject();
        // 输出学生信息
        System.out.println(student);
        // 关闭文件字节输入流
        fis.close();
        // 关闭对象字节输入流
        ois.close();
    

以上是关于Java学习笔记6.3.3 文件操作 - 对象序列化与反序列化的主要内容,如果未能解决你的问题,请参考以下文章

Java学习笔记42(序列化流)

Java 学习笔记 - IO篇:对象流 ObjectInputStreamObjectOutputStream

Java 学习笔记 - IO篇:对象流 ObjectInputStreamObjectOutputStream

Java学习笔记之:Java 流

Java学习笔记--字符串和文件IO

Java学习笔记——序列化和反序列化