乐趣区

关于后端:IO流的序列化和反序列化

何为序列化和反序列化

具体请查阅:IO 流的序列化和反序列化 – 小简博客 (ideaopen.cn)

序列化:指把堆内存中的 Java 对象数据,通过某种形式把对象存储到磁盘文件中或者传递给其余网络节点(在网络上传输)。这个过程称为序列化。艰深来说就是将数据结构或对象转换成二进制串的过程

反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,复原成 Java 对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

如同有些说的不够清晰,咱们缩短一下。

序列化:把对象转换为字节序列的过程称为对象的序列化。(常见的就是存文件)

反序列化:把字节序列复原为对象的过程称为对象反序列化。

再艰深肯定。

  • 序列化:将对象写入到 IO 流中
  • 反序列化:从 IO 流中复原对象

为何会诞生它们

咱们想要学好一个货色,那就必定必须要去理解它的作用和起因。

为了节省时间,我在博客园大佬博客外面找到了一个清晰明了的解释。

JavaBeanSerializeable

程序创立的每个 JavaBean 类都实现 Serializeable 接口。

不晓得你们看到这句话的时候,有没有感到 纳闷?嘿嘿,那就对了,这个你们可能没去理解过。

那咱们当初就来讲一讲这两个玩意,不过,我这篇文章是 解说序列化的,所以我必定不会去本人写一份阐明,这样太费时间了,于是我将会截图知乎上的解说或其余网上博主的阐明。

然而,说的不明确的文章我必定不会抉择的。

咱们看看知乎大佬的阐明!

这就是咱们所说的 JavaBean 的由来了。它或者你们能够完满了解成是一个概念,而非具体的货色。

而咱们想要一个类能够实现 JavaBean 这个概念,咱们须要有如下条件。

1、所有属性为 private

2、提供默认构造方法

3、提供 getter 和 setter

4、实现 serializable 接口

这里咱们就发现了第二个疑难,serializable,这是啥?

这个玩意,是个接口,这个接口有什么用呢?

Serializable接口是一个标记接口,不必实现任何办法 。一旦 实现了此接口,该类的对象就是可序列化的

也就是说,他就是个标记一样,它没什么内容须要你实现,你继承了这个接口,就给了一个标记,有这个标记的类就可序列化。

不过同时也必须满足下面的四个条件才能够!!!

咱们写一段试试。

示范

这就是一个满足序列化的类,这个类我定义了两个字段,最初一个重写是返回了一个 String 值,他就和一般类作用一样,只不过须要满足一些条件。

package IoDemo.Demo;

import java.io.Serializable;

public class IoObj implements Serializable {
    private String str;
    private int num;

    // 默认结构
    public IoObj() {}
    public IoObj(String str, int num) {
        this.str = str;
        this.num = num;
    }

    public String getStr() {return str;}

    public void setStr(String str) {this.str = str;}

    public int getNum() {return num;}

    public void setNum(int num) {this.num = num;}
    @Override
    public String toString() {return "IoObj [str=" + str + ", num=" + num + "]";
    }
    
}

如何序列化

好了,这里有一个反对序列化的类了,咱们当初就来试试如何将这个类的对象序列化。

  • 步骤一:创立一个 ObjectOutputStream 输入流;
  • 步骤二:调用 ObjectOutputStream 对象的 writeObject 输入可序列化对象。

Object是对象的意思,咱们这里能够的序列化与反序列化又能够叫 对象流

咱们先看代码:

package IoDemo.Demo;
import java.io.*;
public class IoDemoTest {public static void main(String[] args) {IoObj ioObj = new IoObj("序列化测试",1);
        // 创立 Object 流对象
        ObjectOutputStream oos = null;
        try {
            // 创立文件对象
            File file = new File("D:\\test.txt");
            // 创立文件输入流对象
            FileOutputStream fos = new FileOutputStream(file);
            // 创立 Object 流对象
            oos = new ObjectOutputStream(fos);
            // 写入对象
            oos.writeObject(ioObj);
        } catch (FileNotFoundException e) {e.printStackTrace();
        } catch (IOException e) {e.printStackTrace();
        }

    }
}

这里我用了几步呢?

新建对象

IoObj ioObj = new IoObj("序列化测试",1);

写入文件的地位

咱们想要将对象写入到 D:\\test.txt,于是咱们须要用File 对象保留地址。

而后咱们还须要将咱们序列化的内容写入到文件,所以咱们还得创立文件的输入流。

// 创立文件对象
File file = new File("D:\\test.txt");
// 创立文件输入流对象
FileOutputStream fos = new FileOutputStream(file);

序列化(创建对象流对象)

// 创立 Object 流对象
oos = new ObjectOutputStream(fos);

这里咱们将下面的输入流对象给了对象流对象。

而后对象流对象有一个办法,是 writeObject() 办法,用于写入对象。

// 写入对象
oos.writeObject(ioObj);

这样,咱们就将 IoObj 对象写入到了文件,咱们看一下。

留神: 当序列化一个对象到文件时,依照 Java 的规范约定是给文件一个 .ser 扩展名。

至于,你可能纳闷,这写入的内容是什么?看不懂。那看看上面的解释就明确了。

反序列化

package IoDemo.Demo;
import java.io.*;
public class IoDemoTest {public static void main(String[] args) {IoObj ioObj = new IoObj("序列化测试",1);
        // 创立 Object 流对象
        ObjectOutputStream oos = null;
        try {
            // 创立文件对象
            File file = new File("D:\\test.txt");
            // 读取文件
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            // 读取对象
            IoObj ioObj1 = (IoObj) ois.readObject();
            System.out.println(ioObj1.toString());
        } catch (FileNotFoundException e) {e.printStackTrace();
        } catch (IOException e) {e.printStackTrace();
        } catch (ClassNotFoundException e) {e.printStackTrace();
        }

    }
}

反序列化同理可知了,我就不多说了。

ObjectInputStream是对象的写入流。

// 读取对象
IoObj ioObj1 = (IoObj) ois.readObject();

这里是将读取的值赋值给对象,readObject()办法就是用于读取对象流文件内容。

至于(IoObj),你能够了解为强转。

扩大

这儿有一位博客园大佬,我也借鉴了它文章,大家能够看看。

大佬的博客

文章有许多扩大知识点,我就间接为了不便浏览,一起搬过去,版权链接下面给了。

反序列化并不会调用构造方法。反序列的对象是由 JVM 本人生成的对象,不通过构造方法生成。

成员是援用的序列化

如果一个可序列化的类的成员不是根本类型,也不是 String 类型,那这个援用类型也必须是可序列化的;否则,会导致此类不能序列化。

如存在这种成员:private Person person

同一对象序列化屡次的机制

同一对象序列化屡次,会将这个对象序列化屡次吗?答案是 否定 的。Java 序列化同一对象,并不会将此对象序列化屡次失去多个对象。

Java 序列化算法
  1. 所有保留到磁盘的对象都有一个序列化编码号
  2. 当程序试图序列化一个对象时,会先查看此对象是否曾经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输入。
  3. 如果此对象曾经序列化过,则间接输入编号即可。

Java 序列化算法潜在的问题

因为 Java 序列化算法不会反复序列化同一个对象,只会记录已序列化对象的编号。如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保留序列化编号。

可选的自定义序列化

有些时候,咱们有这样的需要,某些属性不须要序列化。应用 transient 关键字抉择不须要序列化的字段。

public class Person implements Serializable {
   // 不须要序列化名字与年龄
   private transient String name;
   private transient int age;
   private int height;
   private transient boolean singlehood;
   public Person(String name, int age) {
       this.name = name;
       this.age = age;
   }
   // 省略 get,set 办法
}

public class TransientTest {public static void main(String[] args) throws Exception {try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
            ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {Person person = new Person("9 龙", 23);
           person.setHeight(185);
           System.out.println(person);
           oos.writeObject(person);
           Person p1 = (Person)ios.readObject();
           System.out.println(p1);
       }
   }
}
// 输入后果
//Person{name='9 龙', age=23', singlehood=true', height=185cm}
//Person{name='null', age=0', singlehood=false', height=185cm}

从输入咱们看到,应用 transient 润饰的属性,Java 序列化时,会疏忽掉此字段,所以反序列化出的对象,被 transient 润饰的属性是默认值。对于援用类型,值是 null;根本类型,值是 0;boolean 类型,值是 false。

两种序列化比照
实现 Serializable 接口 实现 Externalizable 接口
零碎主动存储必要的信息 程序员决定存储哪些信息
Java 内建反对,易于实现,只须要实现该接口即可,无需任何代码反对 必须实现接口内的两个办法
性能略差 性能略好

尽管 Externalizable 接口带来了肯定的性能晋升,但变成复杂度也进步了,所以个别通过实现 Serializable 接口进行序列化。

总结
  1. 所有须要网络传输的对象都须要实现序列化接口,通过倡议所有的 javaBean 都实现 Serializable 接口。
  2. 对象的类名、实例变量(包含根本类型,数组,对其余对象的援用)都会被序列化;办法、类变量、transient实例变量都不会被序列化。
  3. 如果想让某个变量不被序列化,应用 transient 润饰。
  4. 序列化对象的援用类型成员变量,也必须是可序列化的,否则,会报错。
  5. 反序列化时必须有序列化对象的 class 文件。
  6. 当通过文件、网络来读取序列化后的对象时,必须依照理论写入的程序读取。
  7. 单例类序列化,须要重写 readResolve() 办法;否则会毁坏单例准则。
  8. 同一对象序列化屡次,只有第一次序列化为二进制流,当前都只是保留序列化编号,不会反复序列化。
  9. 倡议所有可序列化的类加上 serialVersionUID 版本号,不便我的项目降级。
退出移动版