一、什么是Java序列化?


  • 序列化:把Java对象转换为字节序列的过程
  • 反序列:把字节序列恢复为Java对象的过程

image.png

二、为什么需要序列化?

Java对象是运行在JVM的堆内存中的,如果JVM停止后,它的生命也就戛然而止。
image.png
如果想在JVM停止后,把这些对象保存到磁盘或者通过网络传输到另一远程机器,怎么办呢?磁盘这些硬件可不认识Java对象,它们只认识二进制这些机器语言,所以我们就要把这些对象转化为字节数组,这个过程就是序列化啦~

三、序列化用途

序列化使得对象可以脱离程序运行而独立存在,它主要有两种用途:
image.png
1) 序列化机制可以让对象地保存到硬盘上,减轻内存压力的同时,也起了持久化的作用;
2) 序列化机制让Java对象在网络传输不再是天方夜谭。

四、Java序列化常用API

  1. java.io.ObjectOutputStream
  2. java.io.ObjectInputStream
  3. java.io.Serializable
  4. java.io.Externalizable

Serializable 接口

Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。

public interface Serializable {
}

Externalizable 接口

Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}


java.io.ObjectOutputStream类

表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream类

表示对象输入流, 它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。

五、序列化的使用

序列化如何使用?来看一下,序列化的使用的几个关键点吧:

  • 声明一个实体类,实现Serializable接口
  • 使用ObjectOutputStream类的writeObject方法,实现序列化
  • 使用ObjectInputStream类的readObject方法,实现反序列化

    声明一个Student类,实现Serializable

    public class Student implements Serializable {
      private Integer age;
      private String name;
      public Integer getAge() {
          return age;
      }
      public void setAge(Integer age) {
          this.age = age;
      }
      public String getName() {
          return name;
      }
      public void setName(String name) {
          this.name = name;
      }
    }
    
    使用ObjectOutputStream类的writeObject方法,对Student对象实现序列化
    把Student对象设置值后,写入一个文件,即序列化
    ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("D:\\text.out"));
    Student student = new Student();
    student.setAge(25);
    student.setName("jayWei");
    objectOutputStream.writeObject(student);
    objectOutputStream.flush();
    objectOutputStream.close();
    
    image.png

    使用ObjectInputStream类的readObject方法,实现反序列化,重新生成student对象

    再把test.out文件读取出来,反序列化为Student对象
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
    Student student = (Student) objectInputStream.readObject();
    System.out.println("name="+student.getName());
    

六、序列化底层

Serializable底层

image.png
原来底层是这样: ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String?array?enum?还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。所以呀,Serializable真的只是一个标志,一个序列化标志~

writeObject(Object)

序列化的方法就是writeObject,基于以上的demo,我们来分析一波它的核心方法调用链吧~
image.png
writeObject0 主要实现是对象的不同类型,调用不同的方法写入序列化数据,这里面如果对象实现了Serializable接口,就调用writeOrdinaryObject()方法~

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    ......
   //String类型
    if (obj instanceof String) {
        writeString((String) obj, unshared);
   //数组类型
    } else if (cl.isArray()) {
        writeArray(obj, desc, unshared);
   //枚举类型
    } else if (obj instanceof Enum) {
        writeEnum((Enum<?>) obj, desc, unshared);
   //Serializable实现序列化接口
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);
    } else{
        //其他情况会抛异常~
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }
    ......

writeOrdinaryObject()会先调用writeClassDesc(desc),写入该类的生成信息,然后调用writeSerialData方法,写入序列化数据

private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
            ......
            //调用ObjectStreamClass的写入方法
            writeClassDesc(desc, false);
            // 判断是否实现了Externalizable接口
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //写入序列化数据
                writeSerialData(obj, desc);
            }
            .....
    }

七、日常开发序列化的一些注意点

  • static静态变量和transient 修饰的字段是不会被序列化的
  • serialVersionUID问题
  • 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
  • 子类实现了序列化,父类没有实现序列化,父类中的字段丢失问题