Java 对象序列化和反序列化
之前的文章中我们介绍过有关字节流字符流的使用,当时我们对于将一个对象输出到流中的操作,使用DataOutputStream流将该对象中的每个属性值逐个输出到流中,读出时相反。在我们看来这种行为实在是繁琐,尤其是在这个对象中属性值很多的时候。基于此,Java中对象的序列化机制就可以很好的解决这种操作。本篇就简单的介绍Java对象序列化,主要内容如下:
- 简洁的代码实现
- 序列化实现的基本算法
- 两种特殊的情况
- 自定义序列化机制
- 序列化的版本控制
一、简洁的代码实现
在介绍对象序列化的使用方法之前,先看看我们之前是怎么存储一个对象类型的数据的。
//简单定义一个Student类
public class Student {
private String name;
private int age;
public Student(){}
public Student(String name,int age){
this.name = name;
this.age=age;
}
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
//重写toString
@Override
public String toString(){
return ("my name is:"+this.name+" age is:"+this.age);
}
}
//main方法实现了将对象写入文件并读取出来
public static void main(String[] args) throws IOException{
DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
Student stuW = new Student("walker",21);
//将此对象写入到文件中
dot.writeUTF(stuW.getName());
dot.writeInt(stuW.getAge());
dot.close();
//将对象从文件中读出
DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
Student stuR = new Student();
stuR.setName(din.readUTF());
stuR.setAge(din.readInt());
din.close();
System.out.println(stuR);
}
输出结果:my name is:walker age is:21
显然这种代码书写是繁琐的,接下来我们看看,如何使用序列化来完成保存对象的信息。
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
Student stuW = new Student("walker",21);
oos.writeObject(stuW);
oos.close();
//从文件中读取该对象返回
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
Student stuR = (Student)ois.readObject();
System.out.println(stuR);
}
写入文件时,只用了一条语句就是writeObject,读取时也是只用了一条语句readObject。并且Student中的那些set,get方法都用不到了。是不是很简洁呢?接下来介绍实现细节。
二、实现序列化的基本算法
在这种机制中,每个对象都是对应着唯一的一个序列号,而每个对象在被保存的时候也是根据这个序列号来对应着每个不同的对象,对象序列化就是指利用了每个对象的序列号进行保存和读取的。首先以写对象到流中为例,对于每个对象,第一次遇到的时候会将这个对象的基本信息保存到流中,如果当前遇到的对象已经被保存过了,就不会再次保存这些信息,转而记录此对象的序列号(因为数据没必要重复保存)。对于读的情况,从流中遇到的每个对象,如果第一次遇到,直接输出,如果读取到的是某个对象的序列号,就会找到相关联的对象,输出。
说明几点,一个对象要想是可序列化的,就必须实现接口 java.io.Serializable;,这是一个标记接口,不用实现任何的方法。而我们的ObjectOutputStream流,就是一个可以将对象信息转为字节的流,构造函数如下:
public ObjectOutputStream(OutputStream out)
也就是所有字节流都可以作为参数传入,兼容一切字节操作。在这个流中定义了writeObject和readObject方法,实现了序列化对象和反序列化对象。当然,我们也是可以通过在类中实现这两个方法来自定义序列化机制,具体的后文介绍。此处我们只需要了解整个序列化机制,所有的对象数据只会保存一份,至于相同的对象再次出现,只保存对应的序列号。下面,通过两个特殊的情况直观的感受下他的这个基本算法。
三、两个特殊的实例
先看第一个实例:
public class Student implements Serializable {
String name;
int age;
Teacher t; //另外一个对象类型
public Student(){}
public Student(String name,int age,Teacher t){
this.name = name;
this.age=age;
this.t = t;
}
public void setName(String name){this.name = name;}
public void setAge(int age){this.age = age;}
public void setT(Teacher t){this.t = t;}
public String getName(){return this.name;}
public int getAge(){return this.age;}
public Teacher getT(){return this.t;}
}
public class Teacher implements Serializable {
String name;
public Teacher(String name){
this.name = name;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Teacher t = new Teacher("li");
Student stu1 = new Student("walker",21,t);
Student stu2 = new Student("yam",22,t);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
oos.writeObject(stu1);
oos.writeObject(stu2);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
Student stuR1 = (Student)ois.readObject();
Student stuR2 = (Student)ois.readObject();
if (stuR1.getT() == stuR2.getT())
System.out.println("相同对象");
}
结果是很显而易见的,输出了相同对象。我们在main函数中定义了两个student类型对象,他们却都引用的同一个teacher对象在内部。完成序列化之后,反序列化出来两个对象,通过比较他们内部的teacher对象是否是同一个实例,可以看出来,在序列化第一个student对象的时候t是被写入流中的,但是在遇到第二个student对象的teacher对象实例时,发现前面已经写过了,于是不再写入流中,只保存对应的序列号作为引用。当然在反序列化的时候,原理类似。这和我们上面介绍的基本算法是一样的。
下面看第二个特殊实例:
public class Student implements Serializable {
String name;
Teacher t;
}
public class Teacher implements Serializable {
String name;
Student stu;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Teacher t = new Teacher();
Student s =new Student();
t.name = "walker";
t.stu = s;
s.name = "yam";
s.t = t;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
oos.writeObject(t);
oos.writeObject(s);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
Teacher tR = (Teacher)ois.readObject();
Student sR = (Student)ois.readObject();
if(tR == sR.t && sR == tR.stu)System.out.println("ok");
}
输出的结果是ok,这个例子可以叫做:循环引用。从结果我们可以看出来,序列化之前两个对象存在的相互的引用关系,经过序列化之后,两者之间的这种引用关系是依然存在的。其实按照我们之前介绍的判断算法来看,首先我们先序列化了teacher对象,因为他内部引用了student的对象,两者都是第一次遇到,所以将两者序列化到流中,然后我们去序列化student对象,发现这个对象以及内部的teacher对象都已经被序列化了,于是只保存对应的序列号。读取的时候根据序列号恢复对象。
四、自定义序列化机制
综上,我们已经介绍完了基本的序列化与反序列化的知识。但是往往我们会有一些特殊的要求,这种默认的序列化机制虽然已经很完善了,但是有些时候还是不能满足我们的需求。所以我们看看如何自定义序列化机制。自定义序列化机制中,我们会使用到一个关键字,它也是我们之前在看源码的时候经常遇到的,transient。将字段声明transient,等于是告诉默认的序列化机制,这个字段你不要给我写到流中去,我会自己处理的。、
public class Student implements Serializable {
String name;
transient int age;
public String toString(){
return this.name + ":" + this.age;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
Student stu = new Student();
stu.name = "walker";stu.age = 21;
oos.writeObject(stu);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
Student stuR = (Student)ois.readObject();
System.out.println(stuR);
}
输出结果:walker:0
我们不是给age字段赋初始值了么,怎么会是0呢?正如我们上文所说的一样,被transient修饰的字段不会被写入流中,自然读取出来就没有值,默认是0。下面看看我们怎么自己来序列化这个age。
//改动过的student类,main方法没有改动,大家可以往上看
public class Student implements Serializable {
String name;
transient int age;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(25);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
age = ois.readInt();
}
public String toString(){
return this.name + ":" + this.age;
}
}
输出结果:walker:25
结果既不是我么初始化的21,也不是0,而是我们在writeObject方法中写的25。现在我们一点一点看看每个步骤的意义。首先,要想要实现自定义序列化,就需要在该对象定义的类中实现两个方法,writeObject和readObject,而且格式必须和上面贴出来的一样,笔者试过改动方法修饰符,结果导致不能成功序列化。这是因为,Java采用反射机制,检查该对象所在的类中有没有实现这两个方法,没有的话就使用默认的ObjectOutputStream中的这个方法序列化所有字段,如果有的话就执行你自己实现的这个方法。
接下来,看看这两个方法实现的细节,先看writeObject方法,参数是ObjectOutputStream 类型的,这个拿到的是我们在main方法中定义的ObjectOutputStream 对象,要不然它怎么知道该把对象写到那个地方去呢?第一行我们调用的是oos.defaultWriteObject();这个方法实现的功能是,将当前对象中所有没有被transient修饰的字段写入流中,第二条语句我们显式的调用了writeInt方法将age的值写入流中。读取的方法类似,此处不再赘述。
五、版本控制
最后我们来看看,序列化过程的的版本控制问题。在我们将一个对象序列化到流中之后,该对象对应的类的结构改变了,如果此时我们再次从流中将之前保存的对象读取出来,会发生什么?这要分情况来说,如果原类中的字段被删除了,那从流中输出的对应的字段将会被忽略。如果原类中增加了某个字段,那新增的字段的值就是默认值。如果字段的类型发生了改变,抛出异常。在Java中每个类都会有一个记录版本号的变量:static final serivalVersionUID = 115616165165L,此处的值只用于演示并不对应任意某个类。这个版本号是根据该类中的字段等一些属性信息计算出来的,唯一性较高。每次读出的时候都会去比较之前和现在的版本号确认是否发生版本不一致情况,如果版本不一致,就会按照上述的情形分别做处理。
对象的序列化就写完了,如果有什么内容不妥的地方,希望大家指出!
Java 对象序列化和反序列化的更多相关文章
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- Java对象序列化与反序列化一 JSON
Java对象序列化与反序列化一 JSON 1. 依赖库 jackson-all-1.6.1.jar 2. 代码 public class Student { private String nam ...
- Java对象序列化和反序列化的工具方法
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import ja ...
- Java对象序列化与反序列化
对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输.实现的机制是允许将对象转为与平台无关的二进制流. java中对象的序列化机制是将允许对象转为字节序列.这些字节序列可以使Java对象脱离程序存 ...
- java对象序列化、反序列化
平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...
- java对象序列化和反序列化,redis存入和获取对象
最近使用redis发现直接存储序列化后的对象更方便,现提供java序列化和反序列化的代码 1.序列化代码: public static byte[] serialize(Object object) ...
- Java 对象序列化和反序列化 (实现 Serializable 接口)
序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放 ...
- JAVA对象序列化和反序列化学习
JAVA序列化就是将JAVA对象转化为字节序列的过程,而JAVA反序列化就是将字节序列转化为JAVA对象的过程. 这一过程是通过JAVA虚拟机独立完成,所以一个对象序列化后可以在任意时间和任意机器上反 ...
- Java基础—序列化与反序列化(转载)
转载自: Java序列化与反序列化 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化 ...
随机推荐
- MySql学习(七) —— 查询性能优化 深入理解MySql如何执行查询
本篇深入了解查询优化和服务器的内部机制,了解MySql如何执行特定查询,从中也可以知道如何更改查询执行计划,当我们深入理解MySql如何真正地执行查询,明白高效和低效的真正含义,在实际应用中就能扬长避 ...
- web前端面试题及答案
1.常用那几种浏览器测试?有哪些内核(Layout Engine)? 答: (Q1) 浏览器:IE,Chrome,FireFox,Safari,Opera. (Q2) 内核:Trident,Ge ...
- java继承(一)
虽然说java中的面向对象的概念不多,但是具体的细节还是值得大家学习研究,java中的继承实际上就是子类拥有父类所有的内容(除私有信息外),并对其进行扩展.下面是我的笔记,主要包含以下一些内容点: 构 ...
- iOS开发之App主题切换完整解决方案(Swift版)
本篇博客就来介绍一下iOS App中主题切换的常规做法,当然本篇博客中只是提到了一种主题切换的方法,当然还有其他方法,在此就不做过多赘述了.本篇博客中所涉及的Demo完全使用Swift3.0编写完成, ...
- [Kafka] - Kafka内核理解:Message
一个Kafka的Message由一个固定长度的header和一个变长的消息体body组成 header部分由一个字节的magic(文件格式)和四个字节的CRC32(用于判断body消息体是否正常)构成 ...
- 走进javascript——不起眼的基础,值和分号
值 有时我很想知道javascript解析引擎是如何区分一个变量的值,比如下面这段代码. var x = 'javascript'; //javascript x = "hello" ...
- Web Service ,WCF以及Web API的对比
Web Service 1.基于SOAP和XML形式的返回数据. 2.只支出HTTP协议. 3.只能运行在IIS环境下. 4.不是开源的,但可以由任何支持xml的客户端下使用. WCF 1.基于SOA ...
- Ansible详解(二)
Ansible系列命令 Ansible系列命令有如下: ansible:这个命令是日常工作中使用率非常高的命令之一,主要用于临时一次性操作: ansible-doc:是Ansible模块文档说明,针对 ...
- UCSC genome browser 个人track 安装
处理基因组数据,很多时候我们会觉得直接看序列文件不够直观,如果绘图的话,把n多G把数据用画图出来不仅费劲,就算操作也不方便.因此我们可以用UCSC开发出的genome browser,可以直接把数据信 ...
- 史上最全的AJAX
概述 对于web应用程序:用户浏览器发送请求.服务器接收并处理请求,然后返回结果,往往返回就是字符串(HTML),浏览器将字符串(HTML),渲染并显示浏览器上· Ajax和Form表单提交数据的的好 ...