Java:序列化和反序列化

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 方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,836评论 0 24
  • 1. Java序列化和反序列化(What) Java序列化(Serialize)是指将一个Java对象写入IO流中...
    悠扬前奏阅读 859评论 2 1
  • 什么是序列化? 序列化是将对象存储为二进制格式。在序列化的过程中,对象和它的元数据(比如对象的类名和它的属性名称)...
    Chokez阅读 1,099评论 0 0
  • 对象序列化(serialization)和反序列化(deserialization)是将对象转化为便于传输的格式进...
    JerryL_阅读 7,521评论 1 7
  • 官方文档理解 要使类的成员变量可以序列化和反序列化,必须实现Serializable接口。任何可序列化类的子类都是...
    狮_子歌歌阅读 2,386评论 1 3