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

相关文章

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为