Java中的序列化与反序列化
序列化定义
将对象转换为字节流保存起来,并在以后还原这个对象,这种机制叫做对象序列化。
将一个对象保存到永久存储设备上称为持久化。
一个对象要想能够实现序列化,必须实现java.io.Serializable接口。该接口中没有定义任何方法,是一个标识性接口(Marker Interface),当一个类实现了该接口,就表示这个类的对象是可以序列化的。
序列化(serialization)是把一个对象的状态写入一个字节流的过程。当你想要把你的程序状态存到一个固定的存储区域,例如文件时,它是很管用的。
从程序到外面叫序列化,从外面读回来叫反序列化。
序列化特点
假设一个被序列化的对象引用了其他对象,同样,其他对象又引用了更多的对象。这一系列的对象和它们的关系形成了一个顺序图表。
对象序列化和反序列化工具被设计出来并在这一假定条件下运行良好。
如果你试图序列化一个对象图表中顶层的对象,所有其他的引用对象都被循环地定位和序列化;同样,在反序列化过程中,所有的这些对象以及它们的引用都被正确地恢复。
序列化实现细节
只有实现Serializable接口的对象可以被序列化工具存储和恢复。
Serializable接口没有定义任何成员,它只用来表示一个类可以被序列化。
如果一个类可以被序列化,它的所有子类都可以序列化。
当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。
如果一个可序列化的对象包含对某个不可序列化对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。
我们可以将这个不可序列化的引用标记为transient,那么原对象仍然可以序列化。使用transient修饰的变量将不会被序列化。
反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息,在内存中重新构建对象。
ObjectOutput接口
ObjectOutput接口继承DataOutput接口并且支持对象序列化。
特别注意writeObject(Object obj)方法,它被称为序列化一个对象。
所有这些方法在出错情况下引发IOException异常。
ObjectOutputStream类
对象的输出流。ObjectOutputStream类继承OutputStream类和实现ObjectOutput接口。它负责向流写入对象。
该类的构造函数ObjectOutputStream(OutputStream out),参数是序列化对象将要写入的输出流。
根据它的构造函数可以判断它是一个包装类,是一个过滤流。
ObjectInput接口
ObjectInput接口继承DataInput接口。它支持对象序列化。
特别注意readObject()方法,它叫反序列化一个对象。
所有这些方法在出错情况下引发IOException异常。
ObjectInputStream类
ObjectInputStream类继承InputStream类,实现ObjectInput接口。它负责从流中读取对象。
该类的构造函数ObjectInputStream(InputStream in),参数是序列化对象将被读取的输入流。
ObjectInputStream类是一个过滤流,它包装了节点流。
序列化的程序例子SerializableTest1:这个例子定义了一个类叫Person,程序实现了Person类对象的序列化(写出)和反序列化(读入)。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class SerializableTest1
{
public static void main(String[] args) throws Exception
{
Person p1 = new Person("ZhangSan", 29, 1.78);
Person p2 = new Person("LiSi", 50, 1.7);
Person p3 = new Person("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person.txt");// 当前项目路径下的一个文本文档
ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式将对象存储在文件中
oos.writeObject(p1);
oos.writeObject(p2);
oos.writeObject(p3); oos.close(); // 反序列化,将对象信息读出
FileInputStream fis = new FileInputStream("Person.txt");
ObjectInputStream ois = new ObjectInputStream(fis); Person person = null; for (int i = 0; i < 3; ++i)
{
person = (Person) ois.readObject(); System.out.println(person.name + ", " + person.age + ", "
+ person.height);
} ois.close(); } } class Person implements Serializable
{
// 这个类需要实现Serializable接口
// 否则在序列化时会抛出java.io.NotSerializableException
transient String name;
transient int age;
double height; // 加上transient关键字后将不被序列化 public Person(String name, int age, double height)
{
this.name = name;
this.age = age;
this.height = height;
}
}
在程序中尝试可知,如果加上transient关键字,变量将不被序列化,反序列化时读取不到,输出默认值,如整形和double型变量输出为默认值0,字符串将输出为null。
一个序列化中的特殊情况
在序列化和反序列化中需要特殊处理的Serializable类应该实现以下方法:
private void writeObject (java.io.ObjectOutputStream stream) throws IOException
private void readObject (java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
这两个方法不属于任何一个类或任何一个接口,是非常特殊的方法。
只要我们提供了这两个方法,在序列化反序列化的时候它们会得到自动调用。
如根据第一个程序改编的程序2,注意只是将Person类改名为Person2,然后加入上面两个特殊的函数。注意这时候去掉了所有的transient关键字。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class SerializableTest2
{
public static void main(String[] args) throws Exception
{
Person2 p1 = new Person2("ZhangSan", 29, 1.78);
Person2 p2 = new Person2("LiSi", 50, 1.7);
Person2 p3 = new Person2("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person2.txt");// 当前项目路径下的一个文本文档
ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式将对象存储在文件中
oos.writeObject(p1);
oos.writeObject(p2);
oos.writeObject(p3); oos.close(); // 反序列化,将对象信息读出
FileInputStream fis = new FileInputStream("Person2.txt");
ObjectInputStream ois = new ObjectInputStream(fis); Person2 person = null; for (int i = 0; i < 3; ++i)
{
person = (Person2) ois.readObject(); System.out.println(person.name + ", " + person.age + ", "
+ person.height);
} ois.close(); } } class Person2 implements Serializable
{
// 这个类需要实现Serializable接口
// 否则在序列化时会抛出java.io.NotSerializableException
String name;
int age;
double height; // 加上transient关键字后将不被序列化 public Person2(String name, int age, double height)
{
this.name = name;
this.age = age;
this.height = height;
} private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
System.out.println("writeObject Method");
} private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException
{
System.out.println("readObject Method");
} }
程序输出如下:
writeObject Method
writeObject Method
writeObject Method
readObject Method
null, 0, 0.0
readObject Method
null, 0, 0.0
readObject Method
null, 0, 0.0
可以看到这两个方法是自动被调用的,但要注意的是反序列化后得到的值全是默认值。
虽然所有的变量都不是transient关键字修饰,但是程序在序列化时只处理了变量名字,并没有将变量值序列化。
当提供了这两个方法后,序列化和反序列化就完全由我们自己控制。
如果我们在方法中不写入相关代码,则反序列化后并不能读入什么值,所有变量都是默认值。加入相关变量的序列化和反序列化代码后才能真的执行操作。
我们在一个待序列化/反序列化的类中实现了以上两个private方法(方法声明要与上面的完全保持一致,具体在Serializable接口的文档中也有解释),那么就允许我们以更加底层、更加细粒度的方式控制序列化/反序列化的过程。
如下代码,在前面代码的基础上加入了其中两个变量的序列化和反序列化代码。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class SerializableTest2
{
public static void main(String[] args) throws Exception
{
Person2 p1 = new Person2("ZhangSan", 29, 1.78);
Person2 p2 = new Person2("LiSi", 50, 1.7);
Person2 p3 = new Person2("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person2.txt");// 当前项目路径下的一个文本文档
ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式将对象存储在文件中
oos.writeObject(p1);
oos.writeObject(p2);
oos.writeObject(p3); oos.close(); // 反序列化,将对象信息读出
FileInputStream fis = new FileInputStream("Person2.txt");
ObjectInputStream ois = new ObjectInputStream(fis); Person2 person = null; for (int i = 0; i < 3; ++i)
{
person = (Person2) ois.readObject(); System.out.println(person.name + ", " + person.age + ", "
+ person.height);
} ois.close(); } } class Person2 implements Serializable
{
// 这个类需要实现Serializable接口
// 否则在序列化时会抛出java.io.NotSerializableException
String name;
int age;
double height; // 加上transient关键字后将不被序列化 public Person2(String name, int age, double height)
{
this.name = name;
this.age = age;
this.height = height;
} private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
out.writeUTF(name);
out.writeInt(age);
System.out.println("writeObject Method");
} private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException
{
name = in.readUTF();
age = in.readInt();
System.out.println("readObject Method");
} }
参考资料
圣思园张龙老师Java SE系列视频。
Java中的序列化与反序列化的更多相关文章
- java中的序列化与反序列化,还包括将多个对象序列化到一个文件中
package Serialize; /** * Created by hu on 2015/11/7. */ //实现序列化必须实现的接口,这就是一个空接口,起到标识的作用 import java. ...
- K:java中的序列化与反序列化
Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?以下内容将围绕这些问题进行展开讨论. Java序列化与反序列化 简单来说Java序列化是指把Java对象转 ...
- 在Java中进行序列化和反序列化
对象序列化的目标是将对象保存在磁盘中,或者允许在网络中直接传输对象. 对象序列化允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上或者通过网络将这种二进制流传输 ...
- java中的序列化和反序列化学习笔记
须要序列化的Person类: package cn.itcast_07; import java.io.Serializable; /* * NotSerializableException:未序列化 ...
- java中的序列化和反序列化
package cn.zhou; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Fil ...
- 【Java基础】序列化与反序列化深入分析
一.前言 复习Java基础知识点的序列化与反序列化过程,整理了如下学习笔记. 二.为什么需要序列化与反序列化 程序运行时,只要需要,对象可以一直存在,并且我们可以随时访问对象的一些状态信息,如果程序终 ...
- Java对象的序列化与反序列化
序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...
- Java对象的序列化和反序列化[转]
Java基础学习总结--Java对象的序列化和反序列化 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用 ...
- JAVA基础之——序列化和反序列化
1 概念 序列化,将java对象转换成字节序列的过程. 反序列化,将字节序列恢复成java对象的过程. 2 为什么要序列化? 2.1 实现数据持久化,当对象创建后,它就会一直在,但是在程序终止时,这个 ...
随机推荐
- Java中的i++和i--
/** * @Title:DataCate.java * @Package:com.you.dao * @Description:数据类型转换 * @Author: 游海东 * @date: 2014 ...
- faultString = "java.lang.NullPointerException : null"
1.错误描述 (mx.messaging.messages::ErrorMessage)#0 body = (null) clientId = "E75F6AF8-5E0E-4184-3AF ...
- C# 通过smtp服务器进行邮件发送 MailHelper
C# 通过smtp服务器进行邮件发送 MailHelper.cs using System; using System.Data; using System.Configuration; using ...
- 基于puppet分布式集群管理公有云多租户的架构浅谈
基于puppet分布式集群管理公有云多租户的架构浅谈 一.架构介绍 在此架构中,每个租户的业务集群部署一台puppet-master作为自己所在业务集群的puppet的主服务器,在每个业务集群所拥 ...
- canvas实现水波纹效果
本文将会从水波的基本原理开始,详细讲解在canvas中模拟水波扩散,分析并计算水波的能量分布,并通过振幅模拟水波对图像的折射效果,最后实现水波特效. 水波基本原理 首先复习一波高中物理知识. 波是指振 ...
- 使用Spring-boot小结
Spring-boot的特点是,通过注入的方式生成FsShell对象,来操作HDFS,其底层封装了HDFS的的shell命令 1. 添加Spring-boot依赖 pom.xml文件 <!--添 ...
- 【Luogu3919】可持久化数组(主席树)
题面戳我 题解 放一个板子在这里 用主席树维护一下每个版本就可以啦... #include<iostream> #include<cstdio> #include<cst ...
- (2)Deep Learning之线性单元和梯度下降
往期回顾 在上一篇文章中,我们已经学会了编写一个简单的感知器,并用它来实现一个线性分类器.你应该还记得用来训练感知器的『感知器规则』.然而,我们并没有关心这个规则是怎么得到的.本文通过介绍另外一种『感 ...
- 对html进行截图并保存为本地图片
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- LVS-NAT模式的配置详解
由于实验室拟态存储的项目需要通过NAT模式来映射NFS服务器已实现负载均衡的目的,通过调研了多种负载均衡机制,笔者最终选择了LVS的NAT模式来实现需求,接下来通过博客来记录一下LVS-NAT模式的配 ...