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

相关文章

Windows Docker端口占用错误及解决方案总结

《WindowsDocker端口占用错误及解决方案总结》在Windows环境下使用Docker容器时,端口占用错误是开发和运维中常见且棘手的问题,本文将深入剖析该问题的成因,介绍如何通过查看端口分配... 目录引言Windows docker 端口占用错误及解决方案汇总端口冲突形成原因解析诊断当前端口情况解

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Spring 基于XML配置 bean管理 Bean-IOC的方法

《Spring基于XML配置bean管理Bean-IOC的方法》:本文主要介绍Spring基于XML配置bean管理Bean-IOC的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录一. spring学习的核心内容二. 基于 XML 配置 bean1. 通过类型来获取 bean2. 通过

Spring Boot 集成 Quartz并使用Cron 表达式实现定时任务

《SpringBoot集成Quartz并使用Cron表达式实现定时任务》本篇文章介绍了如何在SpringBoot中集成Quartz进行定时任务调度,并通过Cron表达式控制任务... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启动 Sprin

springboot上传zip包并解压至服务器nginx目录方式

《springboot上传zip包并解压至服务器nginx目录方式》:本文主要介绍springboot上传zip包并解压至服务器nginx目录方式,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录springboot上传zip包并解压至服务器nginx目录1.首先需要引入zip相关jar包2.然

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

将Java项目提交到云服务器的流程步骤

《将Java项目提交到云服务器的流程步骤》所谓将项目提交到云服务器即将你的项目打成一个jar包然后提交到云服务器即可,因此我们需要准备服务器环境为:Linux+JDK+MariDB(MySQL)+Gi... 目录1. 安装 jdk1.1 查看 jdk 版本1.2 下载 jdk2. 安装 mariadb(my

SpringBoot中配置Redis连接池的完整指南

《SpringBoot中配置Redis连接池的完整指南》这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以... 目录一、添加依赖二、配置 Redis 连接池三、测试 Redis 操作四、完整示例代码(一)pom.

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思