java的序列化 和 反序列化总结---学习笔记

2024-09-01 20:18

本文主要是介绍java的序列化 和 反序列化总结---学习笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  java的序列化 和 反序列化

1、我们先看一下《java编程思想》第四版中对序列化定义

对象序列化
Java 1.1 增添了一种有趣的特性,名为“对象序列化”( Object Serialization)。它面向那些实现了
Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可
通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows 机器上创
建一个对象,对其序列化,然后通过网络发给一台 Unix 机器,然后在那里准确无误地重新“装配”。不必关
心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现“有限持久化”。请记住“持久化”意味
着对象的“生存时间”并不取决于程序是否正在执行—— 它存在或“生存”于程序的每一次调用之间。通过
序列化一个对象,将其写入磁盘,以后在程序重新调用时重新恢复那个对象,就能圆满实现一种“持久”效
果。之所以称其为“有限”,是因为不能用某种“ persistent”(持久)关键字简单地地定义一个对象,并
让系统自动照看其他所有细节问题(尽管将来可能成为现实)。相反,必须在自己的程序中明确地序列化和
组装对象。
语言里增加了对象序列化的概念后,可提供对两种主要特性的支持。 Java 1.1 的“远程方法调用”( RMI)
使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对
象序列化来传输参数和返回值。 RMI 将在第 15 章作具体讨论。
对象的序列化也是 Java Beans 必需的,后者由 Java 1.1 引入。使用一个 Bean 时,它的状态信息通常在设计
期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化
完成。
对象的序列化处理非常简单,只需对象实现了 Serializable 接口即可(该接口仅是一个标记,没有方法)。
在 Java 1.1 中,许多标准库类都发生了改变,以便能够序列化—— 其中包括用于基本数据类型的全部封装
器、所有集合类以及其他许多东西。甚至 Class 对象也可以序列化(第 11 章讲述了具体实现过程)。
为序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装到 ObjectOutputStream 对象内。此
时,只需调用 writeObject()即可完成对象的序列化,并将其发送给 OutputStream。相反的过程是将一个
InputStream 封装到 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是指向
一个上溯造型 Object 的句柄,所以必须下溯造型,以便能够直接设置。
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄
并保存那些对象;接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对象
网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象
序列化机制,那么在代码里追踪所有这些链接时可能会显得非常麻烦。在另一方面,由于Java 对象的序列化
似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。

下面是序列化的小例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class Person implements Serializable {private static final long serialVersionUID = 1L;private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Person [name=" + name + "]";}public static void main(String[] args)  {Person p=new Person();p.setName("张三");try {File f=new File("d:/3.txt");if(!f.exists()){f.createNewFile();}ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));oos.writeObject(p);oos.flush();oos.close();} catch (Exception e) {e.printStackTrace();// TODO: handle exception}try {ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));Person pp=(Person)ois.readObject();System.out.println(pp);} catch (Exception e) {e.printStackTrace();// TODO: handle exception}}}
结果为:

Person [name=张三]

从上面的代码中可以知道序列化对象读取序列化对象过程中最要的两个函数是OutStream的writeObject和InputStream的readObject ,这个序列化过程是默认的。当然如果有特殊要求可以自己定制序列化。下面是书中例子

2、序列化的控制
正如大家看到的那样,默认的序列化机制并不难操纵。然而,假若有特殊要求又该怎么办呢?我们可能有特殊的安全问题,不希望对象的某一部分序列化;或者某一个子对象完全不必序列化,因为对象恢复以后,那一部分需要重新创建。此时,通过实现 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具体过程。这个
Externalizable 接口扩展了 Serializable,并增添了两个方法: writeExternal()和 readExternal()。在序
列化和重新装配的过程中,会自动调用这两个方法,以便我们执行一些特殊操作。
下面这个例子展示了 Externalizable 接口方法的简单应用。注意 Blip1 和 Blip2 几乎完全一致,除了极微小

的差别(自己研究一下代码,看看是否能发现):

//: Blips.java
// Simple use of Externalizable & a pitfall
import java.io.*;
import java.util.*;
class Blip1 implements Externalizable {
public Blip1() {
System.out.println("Blip1 Constructor");
}
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip1.writeExternal");
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("Blip1.readExternal");
}
}
class Blip2 implements Externalizable {
Blip2() {
System.out.println("Blip2 Constructor");
}
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip2.writeExternal");
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("Blip2.readExternal");
}
}
public class Blips {
public static void main(String[] args) {
System.out.println("Constructing objects:");
Blips b1 = new Blips();
Blip2 b2 = new Blip2();
try {
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"Blips.out"));
System.out.println("Saving objects:");
o.writeObject(b1);
o.writeObject(b2);
o.close();
// Now get them back:
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"Blips.out"));
System.out.println("Recovering b1:");
b1 = (Blips) in.readObject();
// OOPS! Throws an exception:
// ! System.out.println("Recovering b2:");
// ! b2 = (Blip2)in.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
} // /:~

该程序输出如下:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
未恢复 Blip2 对象的原因是那样做会导致一个违例。你找出了 Blip1 和 Blip2 之间的区别吗? Blip1 的构建
器是“公共的”( public), Blip2 的构建器则不然,这样便会在恢复时造成违例。试试将 Blip2 的构建器
属性变成“ public”,然后删除//!注释标记,看看是否能得到正确的结果。
恢复 b1 后,会调用 Blip1 默认构建器。这与恢复一个 Serializable(可序列化)对象不同。在后者的情况
下,对象完全以它保存下来的二进制位为基础恢复,不存在构建器调用。而对一个Externalizable 对象,所
有普通的默认构建行为都会发生(包括在字段定义时的初始化),而且会调用readExternal()。必须注意这
一事实—— 特别注意所有默认的构建行为都会进行— — 否则很难在自己的 Externalizable 对象中产生正确的
行为。
下面这个例子揭示了保存和恢复一个 Externalizable 对象必须做的全部事情:

//: Blip3.java
// Reconstructing an externalizable object
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;class Blip3 implements Externalizable {int i;String s; // No initializationpublic Blip3() {System.out.println("Blip3 Constructor");// s, i not initialized}public Blip3(String x, int a) {System.out.println("Blip3(String x, int a)");s = x;i = a;// s & i initialized only in non-default// constructor.}public String toString() {return s + i;}public void writeExternal(ObjectOutput out) throws IOException {System.out.println("Blip3.writeExternal");// You must do this:out.writeObject(s);out.writeInt(i);}public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {System.out.println("Blip3.readExternal");// You must do this:s = (String) in.readObject();i = in.readInt();}public static void main(String[] args) {System.out.println("Constructing objects:");Blip3 b3 = new Blip3("A String ", 47);System.out.println(b3.toString());try {ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Blip3.out"));System.out.println("Saving object:");o.writeObject(b3);o.close();// Now get it back:ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip3.out"));System.out.println("Recovering b3:");b3 = (Blip3) in.readObject();System.out.println(b3.toString());} catch (Exception e) {e.printStackTrace();}}
} // /:~

其中,字段 s 和 i 只在第二个构建器中初始化,不关默认构建器的事。这意味着假如不在readExternal 中初
始化 s 和 i,它们就会成为 null(因为在对象创建的第一步中已将对象的存储空间清除为 1)。若注释掉跟
随于“ You must do this”后面的两行代码,并运行程序,就会发现当对象恢复以后, s 是 null,而 i 是
零。
若从一个 Externalizable 对象继承,通常需要调用 writeExternal()和 readExternal()的基础类版本,以便
正确地保存和恢复基础类组件。
所以为了让一切正常运作起来,千万不可仅在 writeExternal()方法执行期间写入对象的重要数据(没有默
认的行为可用来为一个 Externalizable 对象写入所有成员对象)的,而是必须在 readExternal()方法中也
恢复那些数据。初次操作时可能会有些不习惯,因为Externalizable 对象的默认构建行为使其看起来似乎正
在进行某种存储与恢复操作。但实情并非如此。

3、transient(临时)关键字


控制序列化过程时,可能有一个特定的子对象不愿让Java 的序列化机制自动保存与恢复。一般地,若那个子
对象包含了不想序列化的敏感信息(如密码),就会面临这种情况。即使那种信息在对象中具有“ private”
(私有)属性,但一旦经序列化处理,人们就可以通过读取一个文件,或者拦截网络传输得到它。
为防止对象的敏感部分被序列化,一个办法是将自己的类实现为Externalizable,就象前面展示的那样。这
样一来,没有任何东西可以自动序列化,只能在writeExternal()明确序列化那些需要的部分。
然而,若操作的是一个 Serializable 对象,所有序列化操作都会自动进行。为解决这个问题,可以用
transient(临时) 逐个字段地关闭序列化,它的意思是“不要麻烦你(指自动机制)保存或恢复它了—— 我
会自己处理的”。
例如,假设一个 Login 对象包含了与一个特定的登录会话有关的信息。校验登录的合法性时,一般都想将数
据保存下来,但不包括密码。为做到这一点,最简单的办法是实现Serializable,并将 password 字段设为
transient。有时候需要序列化的对象中的属性包含有不能序列化的对象。例如下面的例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class Person implements Serializable {private static final long serialVersionUID = 1L;private String name;private Student student;public String getName() {return name;}public void setName(String name) {this.name = name;}public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}@Overridepublic String toString() {return "Person [name=" + name + ", student=" + student + "]";}public static void main(String[] args)  {Person p=new Person();p.setStudent(new Student("张三","4岁"));p.setName("学生");try {File f=new File("d:/3.txt");if(!f.exists()){f.createNewFile();}ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));oos.writeObject(p);oos.flush();oos.close();} catch (Exception e) {e.printStackTrace();// TODO: handle exception}try {ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));Person pp=(Person)ois.readObject();System.out.println(pp);} catch (Exception e) {e.printStackTrace();// TODO: handle exception}}}class Student {private String name;private String age;public Student() {super();// TODO Auto-generated constructor stub}public Student(String name, String age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}}

执行结果报错:

java.io.NotSerializableException: Student

这是由于Student 类不可以序列化, 在这种情况下,可以给Student 添加 transient 关键字,

    private transient Student student;

执行结果:

Person [name=学生, student=null]

如果Student是可以序列化的,Student类实现Serializable,去掉transient 关键字:

执行结果:

Person [name=学生, student=Student [name=张三, age=4岁]]

4 总结
序列化:将java对象转换为字节序列的过程叫做序列化

反序列化:将字节对象转换为java对象的过程叫做反序列化

通过实现 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具体过程,这个
Externalizable 接口扩展了 Serializable,并增添了两个方法: writeExternal()和 readExternal()。

transient 可以使某些属性不被序列化,但是如果是实现了Externalizable接口的类中属性添加 transient也是会被序列化的.



本文参考了<java编程思想>第四版 第10章第九节

这篇关于java的序列化 和 反序列化总结---学习笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127947

相关文章

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义