序列化&反序列化_窝在小角落里学习的博客-程序员宝宝

技术标签: 个人笔记  java  

对象序列化

1、对象序列化概述

序列化是一种对象的传输手段,Java有自己的序列化管理机制。对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象的序列化可以方便地实现对象的传输或存储,如图 1-1所示。

图 1-1

一个类的实例化对象想被序列化,则对象所在的类必须实现java.io.Serializable接口。然而此接口并没有提供任何的抽象方法,所以该接口只是一个标识接口(只是表示一种对象可以被序列化的能力)

范例1:定义序列化对象类Student

//实现Serializable接口
public class Student implements Serializable {
    
    private String name;
    private Integer age;
    private Integer score;

    public Student() {
    
    }

    public Student(String name, Integer age, Integer score) {
    
        this.name = name;
        this.age = age;
        this.score = score;
    }

    public String getName() {
    
        return name;
    }

    public void setName(String name) {
    
        this.name = name;
    }

    public Integer getAge() {
    
        return age;
    }

    public void setAge(Integer age) {
    
        this.age = age;
    }

    public Integer getScore() {
    
        return score;
    }

    public void setScore(Integer score) {
    
        this.score = score;
    }

    @Override
    public String toString() {
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

本程序定义的Student类实现了Serializable接口,所以此类的实例化对象都允许序列化转成二进制进行传输。

tips:对象序列化和对象反序列化操作时的版本兼容问题。

  • 在对象进行序列化或者反序列操作的时候,要考虑JDK版本的问题,如果序列化JDK版本和反序列化JDK版本不统一则有可能造成异常。所以在序列化操作中,引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性,即serialVersionUID是序列化前后的唯一标识符。在进行反序列化时,JVM会把传来的字节流中的serialVerionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
  • 当实现java.io.Serializable接口的实体(类)没有显式地定义一个名为serialVersionUID,类型为long的变量时,Java序列化机制在编译时会根据该类的各方面信息自动产生一个默认的serialVersionUID,一旦更改了类的结构或者信息,则类的serialVersionUID也会跟着改变。
  • 为了serialVersionUID的确定性,写代码时还是建议凡是实现了java.io.Serializable接口的类,最好不要通过编译器来自动生成,人为显式地定义一个名为serialVersionUID,类型为long的变量,那么只要不修改这个变量值的序列化实体都可以相互进行序列化和反序列化。

2、序列化与反序列化处理

序列化和反序列化需要进行二进制存储格式上的统一,为此Java提供了对象序列化操作类。Serializable接口只是定义了某一个类的对象是否被允许序列化,然而对于序列化和反序列化操作的具体实现则需依靠ObjectOutputStreamObjectInputStream两类完成。这两类的继承结构如图 2-1所示。

图 2-1

ObjectOutputStream类可以将对象转为特定格式的二进制数据输出,常用方法如表 2-1所示;ObjectInputStream类可以读取ObjectOutputStream类输出的二进制对象数据,并将其转为具体类型的对象返回,常用方法如表 2-2所示。

表 2-1
方法 类型 描述
public ObjectOutputStream(ObjectOutputStream out) throws IOException 构造 传输输出的对象
public final void writeObject(Object obj) throws IOException 普通 输出对象
表 2-2
方法 类型 描述
public ObjectInputStream(InputStream in) throws IOException 构造 构造输入对象
public final Object readObject() throws IOException, ClassNotFoundException 普通 从指定位置读取对象

范例2:实现对象序列化和反序列化操作

/*
在Student类中添加两个方法
*/

//1、序列化操作
public static void serialize(Object object, String fileName) throws IOException {
    
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(
        new FileOutputStream(new File(fileName)));
    objectOutputStream.writeObject(object);
    objectOutputStream.close();

    System.out.println("序列化成功!");
}

//2、反序列化操作
public static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
    
    ObjectInputStream objectInputStream = new ObjectInputStream(
        new FileInputStream(new File(fileName)));
    Object object = objectInputStream.readObject();
    objectInputStream.close();

    System.out.println("反射序列化成功!");
    return object;
}
//测试类
public class SerializeTestDemo {
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
        Student student = new Student("Chen", 22, 22);
        System.out.println(student);
        //序列化
        Student.serialize(student, "chen.dat");
        System.out.println("==========");

        //反序列化
        Student chen = (Student) Student.deserialize("chen.dat");
        System.out.println(chen);
    }
}

/*
输出结果:
Student{name='Chen', age=22, score=22}
序列化成功!
==========
反射序列化成功!
Student{name='Chen', age=22, score=22}
*/

本程序以二进制文件(chen.dat)作为序列化操作的存储终端 ,在程序中首先通过ObjectOutputStream类将一个Student类的实例化对象输出到文件中,随后再利用ObjectInputStream类将二进制的序列化数据读取并转为Student类实例化对象返回。


3、transient 关键字

默认情况下,当执行对象序列化操作的时候,会将类中的全部属性的内容进行序列化操作,为了保证对象序列化的搞笑传输,就需要防止一些不必要的成员属性的序列化处理。当成员属性不需要进行序列化处理时就可以在属性定义上使用transient关键字来完成。

范例3:transient 关键字的序列化和反序列化操作

//将Student类的score字段定义为transient
private transient Integer score;

//测试类的代码不做任何改变

/*
输出结果:
Student{name='Chen', age=22, score=22}
序列化成功!
==========
反射序列化成功!
Student{name='Chen', age=22, score=null}
*/

本程序对Student类对象进行序列化处理,score属性的内容是不会被保存下来的,这样进行反序列化操作时,score使用的将是其数据类型的默认值。

tips:凡是被static修饰的字段是不会被序列化的。

因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。

4、序列化的受控和加强

序列化和反序列化的过程其实是有漏洞的,因为从序列化到反序列化是有中间过程的,如果被人拿到中间字节流,然后加以伪造或者篡改,那么反序列化返回的实例化对象就会有一定的风险了。毕竟反序列化也是相当于一种**“隐式的”对象构造**,因此,我么希望在反序列时,进行受控的对象反序列化操作。

具体的操作就是自行编写readObject()方法,用于对象的反序列化构造,从而提高约束性。既然是自行编写readObject()方法,那就可以做更多的事情:比如说各种判断工作。

还是以上述的Student类说,一般而言,成绩的取值是在0~100之间,我们为了防止学生的成绩在反序列时被别人篡改,我们可以自行编写readObject()方法用于反序列的控制

/*
在Student类中再添加一个个方法
*/
//自行编写readObject()方法用于反序列的控制
private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
    
        //调用默认的反序列方法
        objectInputStream.defaultReadObject();

        //手工检测反序列化后学生成绩的有效性,若发现问题。立即终止操作!
        if (0 > score || 100 < score) {
    
            throw new IllegalArgumentException("分数只能在0~100之间");
        }
    }
//测试类
public class SerializeTestDemo {
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
        Student student01 = new Student("cbc", 222, 22);
        System.out.println(student01);
        //序列化
        Student.serialize(student01, "cbc.dat");
        //反序列化
        Student cbc = (Student) Student.deserialize("cbc.dat");
        System.out.println(cbc);

        System.out.println("==============================");
        Student student02 = new Student("zhang", 2, 101);
        System.out.println(student02);
        //序列化
        Student.serialize(student02, "zhang.dat");
        //反序列化
        Student zhang = (Student) Student.deserialize("zhang.dat");
        System.out.println(zhang);

    }
}

/*
输出结果:
序列化成功!
反射序列化成功!
Student{name='cbc', age=222, score=22}
==============================
Student{name='zhang', age=2, score=101}
序列化成功!
Exception in thread "main" java.lang.IllegalArgumentException: 分数只能在0~100之间
*/    

对于上面的代码,我一开始也是很懵,怎么自定义为private的readObject()方法也可以被自行调用。后来看到大牛的博客后,看到ObjectStreamClass类最底层的实现,才恍然大悟:又是Java强大的反射机制。如图 4-1。

图 4-1

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Window_mouse/article/details/115101739

智能推荐

C++STL_凉刀出鞘的博客-程序员宝宝_c++stl

STL是什么C++标准:ANSI是ISO组织的美国分支机构。所以,他们是一样的。通常是 ANSI制定标准,然后ISO修改批准,ANSI再修改,所以最后他们就一样了,一般写作ANSI/ISO C++。翻译过来就是标准C++。STL是C++的ANSI/ISO标准的一部分,STL提供了大量的可服用软件组织,例如,程序员再也不用自己设计排序,搜索算法了,这些都已经是STL的一部分了。使用STL编写的代码更容易修改和阅读,因为代码更短了,很多基础工作代码已经被组件化了。STL的组成:三大核心:容器、算

计算机网络的体系结构_小羊咩咩-_-的博客-程序员宝宝

学习目标1.计算机体系结构是什么2.为什么在通信过程中要使用计算机体系结构3.计算机体系结构的具体分类4.利用各种网络体系结构如何具体实现通信1.计算机体系结构是什么计算机网络体系结构的概念:计算机网络各层及协议的集合2.为什莫要在通信过程中使用计算机体系结构因为计算机体系结构强调的是一个分层的概念,所以它能够把通信时需要做的一个整体的拆分各个小的整体需要记住的点是:计算机网络体系结构的分类1.1.osl的体系结构1.2.TCP/IP的体系结构1.

知识梳理:二叉查找树、平衡树_猫猫敲给力的博客-程序员宝宝

知识梳理:二叉查找树一、查找二叉排序树:简称BST也叫二叉搜索树二叉排序树可以是空树。二叉查找树中每个节点:左子树中每个节点的值都不大于该节点值。右子树中每个节点的值都不小于该节点值。二叉排序树的特点:中序遍历二叉排序树,得到一个递增的序列。二叉排序树的结点:与普通树结点一样。过程:1.从根节点开始。2.当前结点非空,看当前结点关键字是否与给定值相等。3.当前结点为空,查找失败。注意:1.查找过程与折半查找过程类似。2.折半查找的判定树就是一棵二叉排序树。二、插入1.插入

jsp里table边框线_JSP好看表格边框_weixin_39541869的博客-程序员宝宝

好文网为大家准备了关于JSP好看表格边框范文,好文网里面收集了五十多篇关于好JSP好看表格边框好文,希望可以帮助大家。更多关于JSP好看表格边框内容请关注好文网篇一:使用JSP做了一个简单的登录框架使用JSP做了一个简单的登录框好文网为大家准备了关于JSP表格边框颜色的文章,好文网里面收集了五十多篇关于好JSP表格边框颜色好文,希望可以帮助大家。更多关于JSP表格边框颜色内容请关注好文网。ctrl...

hoj 2662_你的目光看海的博客-程序员宝宝

链接:http://acm.hit.edu.cn/hoj/problem/view?id=2662 题意: 有一个n*m的棋盘(n、m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。 分析: 经典的状压dp。 设状态dp[n][num][k]: 第n行时,已经放了num个棋子,并且第n行的状态是k。 可得状态转

Symbian S60 第三版真是烦人啊!_weixin_34060741的博客-程序员宝宝

以前在梦飞工作室的时候,朋友给我说Symbian S60系统很爽,几乎整个系统都可以定制。当时没有S60的系统,只是向往哪天能有一个S60的手机,可以看看能够开发一些什么小玩意儿玩玩。当梦想成真的时候一切又灰飞烟灭,买的NOKIA E50是S60第三版的。新的系统带来了更高的安全性,但给开发者带来了开发阻力,特别是业余爱好者,无法得到高级证书,就没办法操作底层的API。也就是说Symbian开始越...

随便推点

【构造】AtCoder Regular Contest 079 D - Decrease (Contestant ver.)_weixin_33691700的博客-程序员宝宝

从n个t变化到n个t-1,恰好要n步,并且其中每一步的max值都&gt;=t,所以把50个49当成最终局面,从这里开始,根据输入的K计算初始局面即可。#include&lt;cstdio&gt;#include&lt;iostream&gt;using namespace std;typedef long long ll;ll K;int main(){ cin&g...

20150317--TP_weixin_30736301的博客-程序员宝宝

20150317--TP 1、 表名操作 在一个数据库中,如果部署了多个项目,那么我们可以使用表前缀解决问题 ‘DB_PREFIX' =&gt;‘think_’ tp中默认的表前缀是think_ 如:数据库表名 我们接下来需要在配置文件config.php...

IOS开发学习-篇外Swift2常用语法-3_杨安康的博客-程序员宝宝

要不是swift的出现我永远都不会学习IOS开发,不过swift的一些基本用法还是需要动手写一写的。//: Playground - noun: a place where people can playimport UIKitvar str = "Hello, playground"var ary:Array<Int> = [10,2,3];ary.sort(>)for i in ary {

Spark:部署和standalone配置调优_xuejianbest的博客-程序员宝宝

spark可以不进行任何配置,直接运行,这时候spark像一个java程序一样,是直接运行在VM中的。spark还支持提交任务到YARN管理的集群,称为spark on yarn模式。spark还支持Mesos管理的集群,Mesos和YARN一样都是管理集群资源的。另外spark自己提供了一种完整的集群管理模式,就是standalone模式。这时候spark的运行不依赖于H...

JVM学习笔记-引用(Reference)机制_Jony-Li的博客-程序员宝宝

如果你还不了解JVM的基本概念和内存划分,请阅读JVM学习笔记-基础知识和JVM学习笔记-内存处理文章。因为Java中没有留给开发者直接与内存打交道的指针(C++指针),所以如何回收不再使用的对象问题,就丢给了JVM。所以接下来就介绍一下目前主流的垃圾收集器所采用的算法。不过在此之前,有必要先了解Reference       1.引用(Reference)如果你现在还是JDK1.0或

Pytorch1全连接网络_微凉code的博客-程序员宝宝

import pandas as pdfrom sklearn.model_selection import train_test_splitimport numpy as npimport torchimport torch.nn as nnfrom torch.optim import SGD,Adamfrom torchviz import make_dotimport torch.utils.data as Dataimport hiddenlayer as hlfrom skle