之前的文章中我们介绍过有关字节流字符流的使用,当时我们对于将一个对象输出到流中的操作,使用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 对象序列化和反序列化的更多相关文章

  1. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  2. Java对象序列化与反序列化一 JSON

    Java对象序列化与反序列化一 JSON 1. 依赖库 jackson-all-1.6.1.jar 2. 代码 public class Student {    private String nam ...

  3. Java对象序列化和反序列化的工具方法

    import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import ja ...

  4. Java对象序列化与反序列化

    对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输.实现的机制是允许将对象转为与平台无关的二进制流. java中对象的序列化机制是将允许对象转为字节序列.这些字节序列可以使Java对象脱离程序存 ...

  5. java对象序列化、反序列化

    平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...

  6. java对象序列化和反序列化,redis存入和获取对象

    最近使用redis发现直接存储序列化后的对象更方便,现提供java序列化和反序列化的代码 1.序列化代码: public static byte[] serialize(Object object) ...

  7. Java 对象序列化和反序列化 (实现 Serializable 接口)

    序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化.  把字节序列恢复为对象的过程称为对象的反序列化. 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放 ...

  8. JAVA对象序列化和反序列化学习

    JAVA序列化就是将JAVA对象转化为字节序列的过程,而JAVA反序列化就是将字节序列转化为JAVA对象的过程. 这一过程是通过JAVA虚拟机独立完成,所以一个对象序列化后可以在任意时间和任意机器上反 ...

  9. Java基础—序列化与反序列化(转载)

    转载自: Java序列化与反序列化 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化 ...

随机推荐

  1. poj1611

    The Suspects Time Limit: 1000MS   Memory Limit: 20000K Total Submissions: 35918   Accepted: 17458 De ...

  2. java测试之文件操作

    package filestream; import java.io.File; public class FileTester { public static void main(String [] ...

  3. c++编程思想(三)--c++中c 续,重点sizeof和内存对齐

    之前理论性的太多,下面就是代码及理论结合了 1.sizeof()是一个独立运算符,并不是函数,可以让我们知道任何变量字节数,可以顺带学一下struct,union,内存对齐 内存对齐:为了机器指令快速 ...

  4. Raid0、Raid1、Raid0+1、Raid3和Raid5 几种磁盘阵列区别

    前两天发现服务器挂了,到机房重启时发现硬盘挂载不上,虽然是开发,但是在交接工作的时候被告知了一点硬件的知识,判断出是硬盘故障.这个呵呵了,修不来只能找服务器售后来换硬盘或是维修了. 关于怎么诊断出硬盘 ...

  5. Animate自定义动画

    在jQuery中出了基本的动画之外,还有用户 可以自定义的函数,Animate() 用于创建自定义动画的函数. apI上指出: 这个函数的关键在于指定动画形式及结果样式属性对象.这个对象中每个属性都表 ...

  6. WP8.1开发中ListView控件加载图列表的简单使用(1)

    我也是刚接触WP编程没几个月,就是在这段时间一直闲着没事,然后又比较喜欢WP这款系统,就学习了WP这方面的开发言语,自学是很困难的,掌握这方面的资料不多,很初级,就是自己在网上找资料学习过程中,看到别 ...

  7. 在Angular中,如果权限值是异步请求所得,如何将其设置为HTTP请求头的Authorization?

    遇到此问题的背景:项目需要实现单点登录,在前后端分离的前提下,前台如何保存token值成为了一个问题.想到的解决方案是,将token值统一存到一个前端程序,其他的前端程序去这个前端程序去取token( ...

  8. 深入理解ajax系列第四篇——请求实例

    前面的话 在使用ajax的过程中,常用的请求方式是GET和POST两种.本文将以实例的形式来详细说明这两种请求方式 GET GET是最常见的请求类型,最常用于向服务器查询某些信息.必要时,可以将查询字 ...

  9. JavaWeb:JSTL

    JSTL 说明 什么是JSTL? JSP标准标签库(JavaServer Pages Standard Tag Library,JSTL)是一个定制的标签库的集合,用来解决像遍历map或者集合.条件测 ...

  10. JQuery实现Ajax跨域访问--Jsonp原理

    JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略).这一策略对于Java ...