美文网首页
Java:序列化和反序列化

Java:序列化和反序列化

作者: 李眼镜 | 来源:发表于2017-11-14 17:30 被阅读0次

1.背景

某天,我在写代码定义 bean 的时候,顺手写了个 public class User implements Serializable,旁边的小哥哥看到了问我:你为什么要实现 Serializable 接口?你哪里用到它了吗?不实现这个接口可以吗?

emmm,皱眉沉思一下,好像也可以?

好吧,那先来了解一下 Serializable 接口涉及到的相关概念。

2.序列化协议+序列化和反序列化

  • 序列化是指:将数据结构或对象转换成特定的格式,使其可以在网络中传输,或可存储在内存/文件中。
    • 序列化后的数据必须是可保持或可传输的数据格式,例如:二进制串/字节流、XML、JSON等。
  • 反序列化:是序列化的逆过程,将对象从序列化数据中还原出来。

自问自答

  • 问:序列化的目的是什么?
  • 答:方便的进行数据的交换和传输工作。

3.JDK类库中的序列化API

Java本身提供了对数据/对象序列化的支持。

  • 输入输出流

    • ObjectOutputStream 对象输出流,其 writeObject(Object obj) 方法可对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
    • ObjectInputStream 对象输入流,其 readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  • 接口

    • 只有实现了 SerializableExternalizable 接口的类的对象才能被序列化。
    • Externalizable 接口继承自 Serializable 接口。
    • 实现 Externalizable 接口的类完全由自身来控制序列化的行为;而仅实现 Serializable 接口的类可以采用默认的序列化方式 。
  • 对象序列化步骤:

    • 创建一个对象输出流。
    • 通过对象输出流的 writeObject() 方法写对象。
  • 对象反序列化步骤:

    • 创建一个对象输入流。
    • 通过对象输入流的 readObject() 方法读取对象。

3.1 对象序列化到文件

// User.java
package com.ann.javas.javacores.serialization.demo1;

import java.io.Serializable;


public class User implements Serializable{

    private static String HH="我是静态变量,我不会被序列化";
    private int userId;
    private String userName;
    private String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public static String getHH() {
        return HH;
    }

    public static void setHH(String HH) {
        User.HH = HH;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo1;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令习习习");
        user.setAddress("北京");

        System.out.println("对象:"+user.toString());
        System.out.println("对象中的静态变量:"+user.getHH());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        User tmp = new User();
        tmp.setHH("我是静态变量,我的值是存在JVM静态存储区的,不是反序列化来的");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("对象:"+user.toString());
        System.out.println("对象中的静态变量:"+user.getHH());
        ois.close();
    }
}

运行结果:

对象:User{userId=1223, userName='令习习习', address='北京'}
对象中的静态变量:我是静态变量,我不会被序列化
序列化成功
反序列化成功
对象:User{userId=1223, userName='令习习习', address='北京'}
对象中的静态变量:我是静态变量,我的值是存在JVM静态存储区的,不是反序列化来的
  • 这是一个简单的序列化和反序列化例子,创建一个 User 实例,将其全部数据序列化到文件;然后再从文件读取数据反序列化为对象。

  • 需要特别关注的是:对象序列化保存的是对象的"状态",即它的成员变量。因此,对象序列化不会关注类中的静态变量。

3.2 隐藏指定字段

在某些场景下,你希望某些字段不要被序列化,此时可以使用 transient 关键字来进行排除。

  • transient 关键字只修饰变量,不修饰方法和类。
  • transient 关键字修饰的变量不再能被序列化,自然也不会被反序列化回来。
// User.java
package com.ann.javas.javacores.serialization.demo2;

import java.io.Serializable;


public class User implements Serializable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}


//Client.java
package com.ann.javas.javacores.serialization.demo2;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令习习习");
        user.setAddress("北京");

        System.out.println("对象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("对象:"+user.toString());
        ois.close();
    }
}

运行结果:

对象:User{userId=1223, userName='令习习习', address='北京'}
序列化成功
反序列化成功
对象:User{userId=1223, userName='令习习习', address='null'}

这里使用 transient 修饰了 Useraddress 变量,因此address不会被序列化,也不会被反序列化。

自问自答

  • 问:使用 transient 修饰的变量,就一定不会被序列化了吗?
  • 答:不一定,要取决于你的程序是怎么写的。

3.3 Serializable 的 readObject 和 writeObject

// User.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class User implements Serializable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(address);
    }
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        address = (String)in.readObject();
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令习习习");
        user.setAddress("北京");

        System.out.println("对象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("对象:"+user.toString());
        ois.close();
    }
}


运行结果:

对象:User{userId=1223, userName='令习习习', address='北京'}
序列化成功
反序列化成功
对象:User{userId=1223, userName='令习习习', address='北京'}

在这个例子中,User 定义了两个private方法:readObject()writeObject()

writeObject() 方法中会先调用 ObjectOutputStream 中的 defaultWriteObject() 方法,该方法会执行默认的序列化机制,此时会忽略掉被 transient 修饰的address字段。然后再调用 writeObject() 方法显示地将address字段写入到 ObjectOutputStream 中。

readObject() 的作用则是针对对象的读取,其原理与 writeObject() 方法相同。

3.4 实现 Externalizable 接口

在Java中,对象的序列化可以通过实现两种接口来实现:

  • 若实现的是 Serializable 接口,则所有的序列化将会自动进行,如果你希望在此基础之上加点自定义的内容,就可以像上面那样加两个方法就ok了。
  • 若实现的是 Externalizable 接口,则没有任何东西可以自动序列化,需要在
    writeExternal() 方法中进行手工指定所要序列化的变量,以及如何序列化,这与是否被 transient 修饰无关(也就是说,当你不需要java自动为你序列化的时候,transient就失效了);当然 readExternal() 也需要做相应的处理。
// User.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;


public class User implements Externalizable{

    private int userId;
    private String userName;
    private transient String address;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(userId + 122);
        out.writeObject(userName);
        out.writeObject(address);
        System.out.println("writeExternal:我没有存原文哦");
        out.flush();
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userId = in.readInt();
        userName = (String)in.readObject();
        address = (String)in.readObject();
    }
}

// Client.java
package com.ann.javas.javacores.serialization.demo3;

import java.io.*;

public class Client {

    public static void main(String[] args) throws Exception {
        toFile();
        fromFile();
    }


    // Object -> 文件
    public static void toFile() throws Exception {
        User user = new User();
        user.setUserId(1223);
        user.setUserName("令习习习");
        user.setAddress("北京");

        System.out.println("对象:"+user.toString());
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("./out.txt")));
        oo.writeObject(user);
        System.out.println("序列化成功");
        oo.close();
    }

    // 文件 -> Object
    public static void fromFile() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./out.txt")));
        System.out.println("反序列化成功");
        User user = (User) ois.readObject();
        System.out.println("对象:"+user.toString());
        ois.close();
    }
}

运行结果:

对象:User{userId=1223, userName='令习习习', address='北京'}
writeExternal:我没有存原文哦
序列化成功
反序列化成功
对象:User{userId=1345, userName='令习习习', address='北京'}

这里有几个关键单需要说明:

  • 实现 Externalizable 接口,一定要自定义序列化方法,如果你把 writeExternal()readExternal() 这里面的实现都丢掉,就会发现,java真的什么都不会做。
  • 如上面所说,当实现了 Externalizable 接口的时候,transient 关键字不再生效。
  • 反序列化时,实际上调用了 User 的无参构造函数,因此在自定义序列化方案的时候,请一定要记得提供一个 公共无参构造函数 ,不然就悲剧了。

4.关于 Serializable 和 Externalizable 的总结和附加说明

  • 构造器:

    • Serializable 序列化时不会调用默认的构造器;
    • Externalizable 序列化时会调用默认构造器。
  • 功能

    • 一个对象想要被序列化,那么它的类就要实现 Serializable 接口,这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。
    • ExternalizableSerializable 接口的子类,有时我们不希望序列化那么多,可以使用这个接口,这个接口的 writeExternal()readExternal() 方法可以指定序列化哪些属性。
  • 关键字

    • 由于 Externalizable 对象默认不保存对象的任何字段,所以 transient 关键字只能伴随 Serializable 使用,虽然 Externalizable 对象中使用 transient 关键字也不报错,但不起任何作用。
  • 方法

    • Serializable 接口的 writeObject()readObject() 方法是可选实现,若没有自定义,则使用默认的。
    • Externalizable 接口的 writeExternal()readExternal() 方法是必选实现,当然你可以在里面什么都不做。

自问自答

  • 问: writeObject()readObject() 都是private方法,它们是怎么被调用的呢?
  • 答:很显然,反射。详情可见 ObjectOutputStream 中的 writeSerialData 方法,以及 ObjectInputStream 中的 readSerialData 方法。

相关文章

  • java专题之序列化

    一、基本概念 1、什么是序列化和反序列化 (1)Java序列化是指把Java对象转换为字节序列的过程,而Java反...

  • Java-序列化-反序列化

    Thanks Java基础学习总结——Java对象的序列化和反序列化java序列化反序列化原理Java 序列化的高...

  • JAVA反序列化漏洞

    目录 反序列化漏洞序列化和反序列化JAVA WEB中的序列化和反序列化对象序列化和反序列范例JAVA中执行系统命令...

  • Java序列化

    Java序列化的几种方式以及序列化的作用 Java基础学习总结——Java对象的序列化和反序列化

  • JDK 序列化

    序列化和分序列化概念 什么是序列化和反序列化 Java序列化是指把Java对象转换为字节序列的过程,而Java反序...

  • Java序列化机制

    Java序列化机制 序列化和反序列化 Java序列化是Java内建的数据(对象)持久化机制,通过序列化可以将运行时...

  • Protostuff序列化和反序列化

    Java序列化和反序列化 序列化和反序列化是在应对网络编程最常遇到的问题之一。序列化就是将Java Object转...

  • Spark序列化

    Java序列化 有关Java对象的序列化和反序列化也算是Java基础的一部分,首先对Java序列化的机制和原理进行...

  • 什么是序列化?常见的序列化协议有哪些?

    序列化和反序列化相关概念 什么是序列化?什么是反序列化? 如果我们需要持久化 Java 对象比如将 Java 对象...

  • 序列化与反序列化

    Java基础学习总结——Java对象的序列化和反序列化 一、序列化和反序列化的概念 把对象转换为字节序列的过程称为...

网友评论

      本文标题:Java:序列化和反序列化

      本文链接:https://www.haomeiwen.com/subject/kpsomxtx.html