JAVA 对象序列化——Serializable(转)
文章出自:http://www.cnblogs.com/chenfei0801/archive/2013/04/05/3001149.html
Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。 只要对象实现了Serializable接口(记住,这个接口只是一个标记接口,不包含任何的方法
如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而饭序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。
对象序列化过程不仅仅保存单个对象,还能追踪对象内所包含的所有引用,并保存那些对象(这些对象也需实现了Serializable接口)。下面这段代码演示了此过程:

package test.serializable; /**
*@chenfeic
*
*只是一个简单的类,用于测试序列化
*/
import java.io.Serializable; public class Data implements Serializable {
private static final long serialVersionUID = 7247714666080613254L;
public int n;
public Data(int n) {
this.n = n;
}
public String toString(){
return Integer.toString(n);
}
}


package test.serializable; import java.io.Serializable;
import java.util.Random; /**
*
* @author chenfei
*
* 用于测试序列化,每个对象Worm对象都与worm中的下一段链接,同时又有属于不同类(Data)的对象引用数组链接
*/
public class Worm implements Serializable {
private static final long serialVersionUID = 5468335797443850679L;
private Data[] d = {
new Data(random.nextInt(10)),
new Data(random.nextInt(10)),
new Data(random.nextInt(10))
};
private static Random random = new Random(47);
private Worm next;
private char c; public Worm(int i , char x) {
System.out.println("Worm constructor:" +i);
c = x;
if(--i > 0) {
next = new Worm(i , (char)(x+1));
}
}
public Worm() {
System.out.println("Default constructor!");
} public String toString() {
StringBuilder sb = new StringBuilder(":");
sb.append(c);
sb.append("(");
for(Data data : d) {
sb.append(data);
}
sb.append(")");
if(next!=null) {
sb.append(next);
}
return sb.toString();
}
}


package test.serializable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class SerializableTest { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Worm w = new Worm(6 ,'a');
System.out.println("序列化操纵之前");
System.out.println("w="+w); //序列化操作1--FileOutputStream
ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));
oos1.writeObject("Worm storage By FileOutputStream ");
oos1.writeObject(w);//必须所有引用的对象都实现序列化(本例终究是Data这个类),否则抛出有java.io.NotSerializableException:这个异常
oos1.close(); //反序列化操作1---FileInputStream
ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));
String s1 = (String)ois1.readObject();
Worm w1 = (Worm)ois1.readObject();
ois1.close();
System.out.println("反序列化操作1之后");
System.out.println(s1);
System.out.println("w1:"+w1); //序列化操作2--ByteArrayOutputStream
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(byteOutStream);
oos2.writeObject("Worm storage By ByteOutputStream ");
oos2.writeObject(w);
oos2.flush(); //反序列操作2--ByteArrayInputStream
ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
ObjectInputStream ois2 = new ObjectInputStream(byteInStream);
String s2 = (String)ois2.readObject();
Worm w2 = (Worm)ois2.readObject();
ois2.close();
System.out.println("反序列化操作2之后");
System.out.println(s2);
System.out.println("w2:"+w2);
} }

运行的结果如下:

Worm constructor:6
Worm constructor:5
Worm constructor:4
Worm constructor:3
Worm constructor:2
Worm constructor:1
序列化操纵之前
w=:a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作1之后
Worm storage By FileOutputStream
w1::a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作2之后
Worm storage By ByteOutputStream
w2::a(853):b(119):c(802):d(788):e(199):f(881)

思考:
1)反序列化后的对象,需要调用构造函数重新构造吗?
答案:不需要。对于Serializable对象,对象完全以它存储的二进制位作为基础来构造,而不调用构造器。
请看下面这段代码

package test.serializable; import java.io.Serializable;
import java.util.Date; /**
*
* @author chenfei
*
* 用于测试序列化时的deep copy
*
*/
public class House implements Serializable {
private static final long serialVersionUID = -6091530420906090649L; private Date date = new Date(); //记录当前的时间 public String toString() {
return "House:" + super.toString() + ".Create Time is:" + date;
} }


package test.serializable; import java.io.Serializable; public class Animal implements Serializable {
private static final long serialVersionUID = -213221189192962074L; private String name; private House house; public Animal(String name , House house) {
this.name = name;
this.house = house;
System.out.println("调用了构造器");
} public String toString() {
return name + "[" +super.toString() + "']" + house;
} }


package test.serializable; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Myworld { /**
* @param args
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
House house = new House();
System.out.println("序列化前");
Animal animal = new Animal("test",house);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(animal);
oos.flush();
oos.close(); System.out.println("反序列化后");
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Animal animal1 = (Animal)ois.readObject();
ois.close();
} }

运行结果如下所示:
序列化前
调用了构造器
反序列化后
从上面的结果中可以看到,在序列化前,当我们使用
Animal animal = new Animal("test",house);
时,调用了Animal的构造器(打印了输出语句),但是反序列后并没有再打印任何语句,说明并没有调用构造器。
2)序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是深复制?)
答案:深复制,反序列化还原后的对象地址与原来的的地址不同。 我们还是看上面思考1)中给出的代码,前两个类不变化,修改第三个类(MyWorld.java)的部分代码,修改后的代码如下:

package test.serializable; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; public class Myworld { /**
* @param args
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
House house = new House();
System.out.println("序列化前");
Animal animal = new Animal("test",house);
System.out.println(animal);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(animal);
oos.writeObject(animal);//在写一次,看对象是否是一样,
oos.flush();
oos.close(); ByteArrayOutputStream out2 = new ByteArrayOutputStream();//换一个输出流
ObjectOutputStream oos2 = new ObjectOutputStream(out2);
oos2.writeObject(animal);
oos2.flush();
oos2.close(); System.out.println("反序列化后");
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Animal animal1 = (Animal)ois.readObject();
Animal animal2 = (Animal)ois.readObject();
ois.close(); ByteArrayInputStream in2 = new ByteArrayInputStream(out2.toByteArray());
ObjectInputStream ois2 = new ObjectInputStream(in2);
Animal animal3 = (Animal)ois2.readObject();
ois2.close(); System.out.println("out流:" +animal1);
System.out.println("out流:" +animal2);
System.out.println("out2流:" +animal3); System.out.println("测试序列化前后的对象 == :"+ (animal==animal1));
System.out.println("测试序列化后同一流的对象:"+ (animal1 == animal2));
System.out.println("测试序列化后不同流的对象==:" + (animal1==animal3)); } }

运行结果如下:

序列化前
调用了构造器
test[test.serializable.Animal@bb7465']House:test.serializable.House@d6c16c.Create Time is:Sat Apr 06 00:11:30 CST 2013
反序列化后
out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013
out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面的相同)
out2流:test[test.serializable.Animal@12cc95d']House:test.serializable.House@157fb52.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面只是值相同,但是地址不一样。)
测试序列化前后的对象 == :false
测试序列化后同一流的对象:true
测试序列化后不同流的对象==:false

从结果可以看到
序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。
补充:
serialVersionUID 的作用?
在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性的时候,那么在某一个对象已经被串行化了,可是这个对象又被修改后重新部署了,那么在这种情况下, 用老软件来读取新文件格式虽然不是什么难事,但是有可能丢失一些信息。 serialVersionUID来解决这些问题,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题。
Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:

public class Save implements Serializable
{
String name; public void save() throws IOException
{
FileOutputStream f = new FileOutputStream("foo");
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
}
}

如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:
java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629
上例异常信息中的数字串表示类定义里各种属性的编码值:
●类的名字(Save)。
●域的名字(name)。
●方法的名字(Save)。
●已实现的接口(Serializable)。
改动上述任意一项内容(无论是增加或删除),都会引起编码值变化,从而引起类似的异常警报。这个数字序列称为“串行化版本统一标识符”(serial
version universal
identifier),简称UID。解决这个问题的办法是在类里面新增一个域serialVersionUID,强制类仍旧使用原来的UID。新增的域必须是:
●static:该域定义的属性作用于整个类,而非特定的对象。
●final:保证代码运行期间该域不会被修改。
●long:它是一个64位的数值。
也就是说,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。
当然,改动之后的类不一定能够和原来的对象兼容。例如,如果把一个域的定义从String改成了int,执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException:
Save; incompatible types for field name。
JAVA 对象序列化——Serializable(转)的更多相关文章
- JAVA 对象序列化——Serializable
1.序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来.虽然你可以用你自己的各种各样的方法来保存object st ...
- Java对象序列化剖析
对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...
- 理解Java对象序列化
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- java 对象序列化
java 对象序列化 package org.rui.io.serializable; import java.io.ByteArrayInputStream; import java.io.Byte ...
- 理解Java对象序列化(二)
关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制.在撰写本文时,既参考了Th ...
- 关于 Java 对象序列化您不知道的 5 件事
数年前,当和一个软件团队一起用 Java 语言编写一个应用程序时,我体会到比一般程序员多知道一点关于 Java 对象序列化的知识所带来的好处. 关于本系列 您觉得自己懂 Java 编程?事实上,大多数 ...
- java 对象序列化 RMI
对于一个存在于Java虚拟机中的对象来说,其内部的状态只保持在内存中.JVM停止之后,这些状态就丢失了.在很多情况下,对象的内部状态是需要被持久化下来的.提到持久化,最直接的做法是保存到文件系统或是数 ...
- Java对象序列化入门
Java对象序列化入门 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结.此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制 ...
随机推荐
- easyui 扩展layout的方法,支持动态添加删除块
$.extend($.fn.layout.methods, { remove: function(jq, region){ return jq.each(function(){ var panel = ...
- redux sample with redux source code
code sample没有package.json文件,也就没有任何外部依赖,直接使用redux source code. nodejs对es6的import export还不支持,这里使用了stac ...
- Otter双A同步搭建入门教程
1.准备阶段 1.1 环境 虚拟机A :192.168.0.130 虚拟机B :192.168.0.131 系统:Ubuntu 16.04 LTS JRE:OpenJDK 1.8.0_151(A.B都 ...
- sql实际应用-递归查询
1.既然要谈到sql,数据库表是必须的 2.数据结构 3.获取某个节点的所有子节点 传统的写法(sql2000) 很麻烦,暂且就不写了 来看看CTE的写法 CREATE PRO ...
- python入门(三):分支、循环、函数
1.分支 if循环格式:if condition_1: statement_block_1elif condition_2: statement_block_2else: statement_bloc ...
- Amazon【EC2】如何启动一个新的EC2实例
1.1 控制台可直接搜索ec2 1.2 创建实例 aws没有新建实例这个说法,新建就是启动实例 1.3 选择镜像 在搜索框中搜索自己想要的镜像.这里有收费的也有免费的 1.4 选择实例类型 选择自己想 ...
- jstat 监控调整GC很好用
jstat命令使用 jstat命令可以查看堆内存各部分的使用量,以及加载类的数量.命令的格式如下: jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数] 注意:使用的jdk版本是 ...
- java中的字符集和编码
前言 上次对计算机中的“字符集”和“编码”分别进行了总结,并指出二者之间的区别,不要搞混了,不清楚的再回到上一章看一下.今天再总结下java中是如何使用字符集(主要是Unicode字符集,其他常用字符 ...
- docker菜鸟入门
.Docker概念简介 1.1 Docker的应用场景 1.2 Docker的优势 1.3 Docker架构 1.4 Dorcker核心概念 Dockerfile.Docker镜像和Docker容器的 ...
- Windows按名称排序问题
偶然发现一个按名称排序的文件夹内,文件顺序是混乱的,例如: 在一个文件夹内建立如下三个文件: 0F.txt 1A.txt 02.txt 按名称/升序排列, 将得到上述结果,0F在最前,02在最后. 百 ...