Java序列化、反序列化

前言

  说到序列化可能很多开发人员对此并不清楚,甚至没有了解,作为一个专业的程序员怎么能不懂这个呢,今天就来聊聊这个,我们都知道,程序运行时对象数据在内存中,程序关闭后,数据就不存在了,这个时候我们希望对象数据能够进行持久保存,需要的时候根据那就拿出来用。那就用到了序列化,通过序列化我们可以将对象数据转换为可取用的格式(比如二进制、JSON)存储到磁盘,或者通过网络传输到另一条机器上,在需要的时候在还原到内存中,这个过程叫反序列化。贴两句专业描述:

序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。

从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

知道了什么是序列化,那如何实现序列化和反序列化呢?

实现序列化和反序列化

为了使对象可序列化,需实现java.io.Serializable接口或者Externalizable接口。

  • 对于使用Serializable声明的类,对象序列化可以自动保存和还原对象每个类的字段,并通过添加字段或超类型来自动处理不断演变的类。一个可序列化的类可以声明要保存或还原其哪些字段,以及写入和读取可选值和对象。
  • 对于使用Externalizable声明的类,对象序列化将完全控制其外部格式以及如何保存和还原超类型的状态委托给该类。

实现接口是不够的,我们还需要通过对象流来对数据进行读取和写入:

ObjectOutputStream类包含用于序列化对象的writeObject()方法。

public final void writeObject(Object obj)  throws IOException

ObjectInputStream类包含用于反序列化对象的readObject()方法。

public final Object readObject()  throws IOException, ClassNotFoundException

  关于序列化属性的要求,默认可序列化属性字段被声明为非transient和非static字段。在可序列化的类中声明serialPersistentFields字段,可以覆盖此默认计算 serialPersistentFields。必须使用ObjectStreamField列出目标对象和可序列化字段的类型的对象数组来初始化该字段。字段的修饰符必须是privatestatic的和final的,如果该字段的值为null或着不是此实例的ObjectStreamField[],或不存在privatestaticfinal修饰符,那么此定义没有任何作用。

serialVersionUID

  serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的serialVersionUID不一致,进行反序列时会抛出 InvalidClassException
  如果序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面为该类计算一个默认值。那么重新编译后的serialVersionUID将会发生变化,任何地方的更改都会影响到序列化的数据,因此建议所有的序列化类显式的声明serialVersionUID值。
  建议对UID使用private修饰符,因为它作为继承属性没有用。

  • 如果父类已经实现了Serializable接口,则子类不需要实现它,反之亦然。
  • 仅非静态属性通过序列化过程可以保存。
  • 静态属性和临时属性不会通过序列化过程保存。因此,如果不想保存非静态数据属性值,需要加入transient声明。
  • 反序列化对象时,永远不会调用对象的构造函数。
  • 关联对象必须实现Serializable接口。

用例

定义User


import lombok.Data;
import java.io.Serializable;

/**
 * User
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 11:42
 */
@Data
public class User implements Serializable {
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 4760229593345099240L;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 住址
     */
    private transient String address;

    public User(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

原生方式
序列化对象
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.logging.Logger;

/**
 * 序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 11:43
 */
public class Serialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File("user.txt");
        User user = new User("小美", 18);
        //将对象保存在文件中
        FileOutputStream fileOut = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        //对象序列化的方法
        out.writeObject(user);
        //关闭
        out.close();
        fileOut.close();
        LOGGER.info("对象已被序列化");
    }
}

运行程序,在当前项目下找到user.txt文件,打开查看,即可看到序列化后的内容。

反序列化
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.logging.Logger;

/**
 * 反序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 15:21
 */
public class Deserialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();


    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //从文件中读取对象
        File file = new File("user.txt");
        FileInputStream fileInput = new FileInputStream(file);
        ObjectInputStream in = new ObjectInputStream(fileInput);
        //对象反序列化的方法
        User userRead = (User) in.readObject();
        //关闭
        fileInput.close();
        in.close();
        LOGGER.info("对象已被反序列化" + " " + userRead.toString());
    }
}

控制台输出,可以看到将刚才对象中的数据反序列化了出来,由于address属性我们声明了transient,序列化并没有保存。

信息: 对象已被反序列化 User(name=小美, age=18, address=null)
serialPersistentFields

在上述基础上,测试声明serialPersistentFields情况。

  • 定义为null,序列化后在反序列化,打印出了对象字段数据,无效
private static final ObjectStreamField[] serialPersistentFields = null;
  • 定义User类不存在字段、序列化抛出java.io.InvalidClassException 异常
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("name_",String.class)};
  • 声明为{}没有序列化任何字段,序列化、序列化后进行打印,对象字段数据为null,生效
private static final ObjectStreamField[] serialPersistentFields = {};
  • 只配置序列化name字段,序列化后反序列化打印,对象输出只有name有值,生效。
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("name",String.class)};
JSON序列化

   JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。JSON序列化就是将数据对象转为JSON字符串,JSON的可读性更好,方便调试。广泛使用的JSON库有Jacksonfastjson
   JSON序列化通常会通过网络传输对象、而对象中往往会存在敏感数据,所以序列化常常成为黑客攻击点,攻击者巧妙的利用反序列化过程构造恶意代码,是程序在反序列化过程中执行任意代码,Jacksonfastjson 都曾经出现过反序列化漏洞,那么如何防范攻击呢?有些敏感属性不需要进行序列化传输,可以加transient关键字声明,避免此属性序列化,如果一定要传递敏感数据,可以使用对称和非对称加密传输,在进行解密处理后还原到对象中。开发者一定要有安全防范意识,对传入数据内容进行校验或权限控制,及时更新JSON库安全漏洞,避免受到攻击。在工程中目前自己常用的JSON库是fastjson,fastjson可以通过定制序列化对需要序列化的数据进行处理。fastjson定制序列化

序列化
import com.alibaba.fastjson.JSON;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Logger;

/**
 * JSON序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 17:55
 */
public class Serialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException {
        //创建对象
        User user = new User("小美", 18, "山东省济南市");
        //序列化
        String json = JSON.toJSONString(user);
        //写入文件
        FileOutputStream fileOut = new FileOutputStream(new File("user.json"));
        fileOut.write(json.getBytes());
        LOGGER.info("序列化对象完成");
    }
}

打开项目根目录下的user.json,便可以看到对象序列化后的JSON串。

反序列化
import com.alibaba.fastjson.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Logger;

/**
 * JSON反序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 17:55
 */
public class Deserialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException {
        //读取文件
        FileInputStream fileOut = new FileInputStream(new File("user.json"));
        int len;
        byte[] buf = new byte[1024];
        while ((len = fileOut.read(buf)) != -1) {
            //反序列化
            User user = JSONObject.parseObject(new String(buf, 0, len), User.class);
            LOGGER.info(user.toString());
        }
        LOGGER.info("反序列化对象完成");
    }
}

控制台输出打印的对象,由于address 属性使用transient声明,所以并没有被序列化,所以也不会反序列化,所以是null

信息: User(name=小美, age=18, address=null)

JSON序列化应用场景是很多的,其中在自己负责的项目里,强制规定了对象日志打印格式为JSON,因为出现问题非常方便查看并 Debug,包括spring data redis 序列化用的也是JSON方式。

参考

Java Serializable Doc
oracle serialization doc

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

推荐阅读更多精彩内容