在一些情况下,如果对象能够在程序不运行的情况下仍能存在并保存其信息,这样在下次运行程序时,该对象将被重建,并且拥有的信息与程序在上次运行时它所拥有的信息相同,这将非常有用。“将一个对象编码成一个字节流”,称为对象序列化,相反的处理过程称为反序列化。一旦对象被序列化后,它的编码就可以从一台正在运行的虚拟机被传递到令一台虚拟机上,或者被存储到磁盘上,供以后反复使用。Java提供了内建的语言机制来实现序列化,在Java的序列化中涉及到两个接口和一个关键字,分别是Serializable接口和Externalizable接口,以及transient关键字。
Serializable接口
Serializable是一个标记接口,它不包含任何方法,只要对象实现了Serializable接口,对象的序列化就会非常简单。在Java中,序列化是语言特性,几乎不需要我们做什么,虚拟机就会正确的完成对象的序列化。来看一个小小的例子:
public class SerializableDemo implements Serializable {
private String name;
private String password;
//在反序列过程中没有被调用,序列化是语言外的对象创建机制
public SerializableDemo() {
System.out.println("Defualt constructor");
}
public SerializableDemo(String name,String pw) {
System.out.println("execute SerializableDemo(String name,String pw)");
this.name = name;
this.password = pw;
}
public String toString() {
return "{ name: " + name +";password: " + password + " }";
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializableDemo instance = new SerializableDemo("cxy","123456");
System.out.println(instance);
//将对象序列化到文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./bin/com/think/io/serializableDemo.txt"));
//可以写入基本类型
out.writeInt(10001);
//将对象写入
out.writeObject(instance);
out.close();
//反序列化,生成对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("./bin/com/think/io/serializableDemo.txt"));
//反序列化得到的对象,必须按写入的顺序读
int no = in.readInt();
SerializableDemo deseri = (SerializableDemo) in.readObject();
System.out.println(deseri);
//生成了不同的对象
System.out.println("instance == deseri : " + (instance == deseri));
}
}
在上面的代码中,SerializableDemo类实现了Serializable接口,它有两个private域。我们演示了把对象持久到文件中,然后再从文件中把对象重建,在使用ObjectInputStream时,获取对象(或基本类型)的顺序必须与写入顺序一致。我们还应该注意到,重建的对象和原来的对象是两个不同的对象。实现Serializable接口实现的序列化,是一种默认的自动序列化机制,它会把所有的属性全部序列化,这一切都是自动发生的;在重建的过程中,它没有调用任何构造方法,序列化是一种语言外的对象创建机制。
transient关键字
在前面的例子中,我们看到Serializable接口是一种自动化序列机制,它会把所有的域全部序列化,即使是private的。这样会存在一个问题,我们不希望序列化某些敏感信息(如密码),因为一经序列化,人们就可以通过读取文件或者拦截网络传输的方法访问到它。有一种方法可以防止对象的敏感部分被序列化,那就是实现Externalizable接口,稍后会看到这样的例子。如果在Serializable接口中,我们需要transient(瞬时)关键字的帮忙,当使用transient关键字修饰字段时,它会关闭该字段序列化,它隐含的意思是:我不需要序列化,反序列化时,把我设置零值就行了。比如前面的例子中,password属性敏感信息,为了阻止对它的序列化,使用transient:
private transient String password;
这样在反序列过程中创建的对象deseri,其pawwsord字段将是null。由于Externalizable接口不自动序列化,所以transient关键字只能与Serializable接口配合使用。
Externalizable接口
像上面的例子所列举的那样,如果想对序列化进行更多的控制,使用Serializable接口显然是不合适的,可以实现Externalizable接口来对序列化过程进行控制。Externalizable接口有两个方法,writeExternal()和readExternal(),分别用来控制字段的序列化和反序列化。看一下Externalizable接口:
/**
* Externalizable继承自Serializable接口
* 默认不自动序列化
*/
public interface Externalizable extends java.io.Serializable {
/**
* 该对象可实现 writeExternal 方法来保存其内容,
* 它可以通过调用 DataOutput 的方法来保存其基本值,
* 或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。
* @param out 要写入对象的流
* @throws IOException
*/
void writeExternal(ObjectOutput out) throws IOException;
/**
* 对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 的方法来恢复其基础类型,
* 调用 readObject 来恢复对象、字符串和数组。
* readExternal 方法必须按照与 writeExternal 方法写入值时使用的相同顺序和类型来读取这些值。
* @param in 为了恢复数据而从中读取数据的流
* @exception IOException if I/O errors occur
* @exception ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
Externalizable将序列化的控制交由程序员来控制,权利意味着义务,程序员必须要做额外的工作才能正确的完成对象的序列化,在上面的例子中,如果把实现Serializable接口改成实现Externalizable接口,而不做额外的控制,那么反序列化得到的对象的name和password字段将是null。为了正确的序列化,必须要在writeExternal()中显式的把需要序列化的字段写入流中,在反序列化时,要在readExternal()中显式的从流中读取字段值。Externalizable的序列化过程和Serializable的不同,如下的代码将展示其中的差异:
public class ExternalizableDemo implements Externalizable {
//内部类,用来说明字段定义处的初始化在反序列化的过程中也会得到执行
private static class Helper {
Helper(String content) {
System.out.println(content);
}
}
private Helper h = new Helper("字段定义处的初始化");
private String name;
private String password;
//在反序列化的过程中,语句块中的初始化会被执行
{
System.out.println("******语句块中的内容********");
}
//在反序列化的过程中,默认构造方法会被执行
public ExternalizableDemo() {
System.out.println("Defualt constructor");
}
public ExternalizableDemo(String name,String pw) {
System.out.println("execute SerializableDemo(String name,String pw)");
this.name = name;
this.password = pw;
}
public String toString() {
return "{ name: " + name +";password: " + password + " }";
}
/**
* 需要在这里显式的调用writeXXX将字段序列化
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
}
/**
* 需要在这里显式的调用readXXX将字段反序列化
* 同时把获取的值赋给字段
*/
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = (String)in.readObject();
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ExternalizableDemo instance = new ExternalizableDemo("cxy","123456");
System.out.println(instance);
//将对象序列化到文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./bin/com/think/io/external.txt"));
//可以写入基本类型
out.writeInt(10001);
//将对象写入
out.writeObject(instance);
out.close();
System.out.println("\n反序列化开始.\n");
//反序列化,生成对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("./bin/com/think/io/external.txt"));
//反序列化得到的对象,必须按写入的顺序读
int no = in.readInt();
ExternalizableDemo deseri = (ExternalizableDemo) in.readObject();
System.out.println(deseri);
//生成了不同的对象
System.out.println("\ninstance == deseri : " + (instance == deseri));
}
}
上面程序的执行结果:
字段定义处的初始化
******语句块中的内容********
execute SerializableDemo(String name,String pw)
{ name: cxy;password: 123456 }
反序列化开始.
字段定义处的初始化
******语句块中的内容********
Defualt constructor
{ name: cxy;password: null }
instance == deseri : false
我们可以看到,在反序列化的过程中调用了默认构造器,这与反序列Serializable对象的不同。对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器;而对于Externalizable对象,所有的普通的默认构造器都会被调用(无参构造器,字段定义处的初始化,语句块初始化),然后才调用readExternal()来反序列对象。序列化的过程完全在我们的控制中,在上面的例子中,没有将password序列化,那么反序列化得到的对象的password就为null。
我们还应注意到,readExternal()和writeExternal()是自动被调用的。
奇怪的序列化形式
Java提供了一种方式,使程序员拥有更多对Serializable对象序列化的控制,就像对Externalizable对象一样:给实现Serializable接口的对象添加writeObject()和readObject()方法,这样一旦对象被徐立华或者被反序列化时,就会自动地调用这个两个方法,而不是使用默认的序列化机制。这两个方法必须具有准确的方法特征签名:
private void readObject(ObjectInputStream stream) throws IOException;
private void writeObject(ObjectOutputStream stream) throws IOExcpeiton,ClassNotFoundException;
在调用ObjectOutputStream.writeObject(Object obj)时,VM会检查所传递的Serializable对象,看看是否实现了自己的writeObject(ObjectOutputStream stream)方法,如果实现了该方法,就跳过正常的序列化过程并调用它的writeObject(ObjectOutputStream stream);readObject()的过程与此类似。只是添加的这两个方法,与序列化时调用的writeObject()和readObject()方法名称相同,实在是很容易混淆。添加的两个方法都是private,其使用方法与Externalizable接口的writeExternal()和readExternal()类似,下面展示一个使用它的例子:
public class SerializableAdd implements Serializable {
private String name;
private String no;
private transient String password;
//添加的序列化方法
private void writeObject(ObjectOutputStream stream)throws IOException {
//普通的字段可以使用默认的序列机制
//注意:defaultWriteObject()只能在序列化的writeObject()方法中调用
//stream.defaultWriteObject();
//也可以调用writeXXX()
stream.writeObject(name);
stream.writeObject(no);
//对于transient方法就可以显式控制
stream.writeObject(password);
}
//添加的反序列化方法
private void readObject(ObjectInputStream stream)
throws IOException,ClassNotFoundException {
//注意:defaultReadObject()只能在序列化的readObject()方法中调用
stream.defaultReadObject();
password = (String)stream.readObject();
}
}
实现深拷贝
对象的序列化并仅仅只是保存了目标对象的内容,它能追踪目标对象内所包含的所有引用,并保存那些对象,并且还是递归的追踪,比如A包含B,B包含C,在反序列化A时,A里的B同样会得到反序列化,B里的C也能得到反序列化,只要B和C都实现了Serializable接口或者Externalizable接口。为了实现深拷贝,还有两个问题没有弄明白:反序列化得到的对象与原始对象是同一个对象吗?他们指向的第三个对象引用(上例中的B,C)是相同的吗?下面的程序可以解答这两个问题:
public class ReferenceTest implements Serializable{
//必需实现Serializable接口,否则不能序列化
private static class Data implements Serializable{
private long id = 10001;
public void setId(long id) {
this.id = id;
}
public long getId() {
return id;
}
}
private Data d;
public ReferenceTest(Data d) {
this.d = d;
}
public Data getD() {
return d;
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Data d = new Data();
ReferenceTest origin = new ReferenceTest(d);
System.out.println("origin.hashCode(): " + origin.hashCode());
//将对象持久到文件中
ObjectOutputStream out1 = new ObjectOutputStream(
new FileOutputStream("./bin/com/think/io/seria1.txt"));
//把同一个对象写入两次
out1.writeObject(origin);
out1.writeObject(origin);
out1.close();
ObjectInputStream in1 = new ObjectInputStream(
new FileInputStream("./bin/com/think/io/seria1.txt"));
ReferenceTest deseri = (ReferenceTest)in1.readObject();
ReferenceTest singleStream = (ReferenceTest) in1.readObject();
System.out.println("deseri.hashCode(): " + deseri.hashCode());
//原始对象与序列化对象是不同的对象
System.out.println("origin == deseri: " + (origin == deseri));
//引用的对象同样不同
System.out.println("origin.getD() == deseri.getD() : " + (origin.getD() == deseri.getD()));
//同一个流中序列化的同一个对象,反序列得到的对象相同吗?
System.out.println("deseri == singleStream: " + (deseri == singleStream));
//把同一个对象序列化到不同的地方,反序列化得到的对象时相同的吗?
//把origin持久到另一个地方
ByteArrayOutputStream buff = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(buff);
out2.writeObject(origin);
out2.close();
ByteArrayInputStream buff_in = new ByteArrayInputStream(buff.toByteArray());
ObjectInputStream in2 = new ObjectInputStream(buff_in);
ReferenceTest other = (ReferenceTest)in2.readObject();
System.out.println("other.hashCode(): " + other.hashCode());
System.out.println("deseri == other: " + (deseri == other));
}
}
ReferenceTest持有一个Data的引用,注意Data必须实现Serilizable接口,否则不能序列化。根据运行结果,为了更清楚的看到真相,打印了每个对象的hashCode(),如果两个对象是相同的,一下是输出结果:
origin.hashCode(): 20545604
deseri.hashCode(): 18979724
origin == deseri: false
origin.getD() == deseri.getD() : false
deseri == singleStream: true
other.hashCode(): 23804217
deseri == other: false
于是我们得到一下结论:
(1)反序列化得到的对象与原始对象不是同一个对象,这是我们希望得到的结果;
(2)对象中持有的引用对象也是不相同的,这同样是我们想要的结果;
(3)同一个对象持久到不同的流中,并从中反序列化得到的对象也不相同,这是一个合理的结果,一个流不可能知道另一个流里的内容,deseri==other为false。不过同一个流里两次序列的同一个对象,反序列得到的两个对象是相同的,上面例子中deseri==singleStream为true,验证这一条。
ClassNotFound异常
在反序列化的过程中,如果在classpath下找不到反序列化对象对应的.class文件,就会抛出ClassNotFoundException。
以上只是对Java序列化用法的介绍,并不涉及更多深层次的内容,我们可以看到Java的序列化是一个非常有用的特性。
转载请注明:喻红叶《Java序列化》
分享到:
相关推荐
Java序列化是Java平台中的一种持久化机制,它允许对象的状态被转换成字节流,以便存储、网络传输或在不同时间点恢复。这个过程被称为序列化,而反向操作称为反序列化。序列化在许多场景下都非常有用,比如在分布式...
java 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...
【Protocol Buffer序列化对比Java序列化】 Protocol Buffer(简称PB)是Google开发的一种高效的数据序列化协议,而Java序列化是Java平台内置的一种序列化机制。两者的主要目标都是将对象转化为字节数组,便于在网络...
Java序列化是Java平台提供的一种持久化机制,它允许我们将一个Java对象转换为字节流,以便存储到磁盘上,或者通过网络进行传输。这使得我们可以保存和恢复对象的状态。实现序列化的类需要实现`Serializable`接口,...
Java序列化是Java平台中的一种核心机制,它允许对象的状态被转换成字节流,以便存储到磁盘、数据库,或者在网络中进行传输。这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...
Java序列化是Java平台中的一种标准机制,它允许将对象的状态转换为字节流,以便存储、传输或恢复。在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将...
Java序列化是Java平台中的一项重要技术,它允许对象的状态被转换为字节流,以便存储或通过网络进行传输。这种技术在分布式系统、持久化存储以及数据交换等场景中非常常见。本资源包含了三个流行的Java序列化框架:...
Java序列化是Java平台提供的一种将对象转换为字节流,以便存储、在网络上传输或者在后续时间重新创建相同对象的机制。这是Java编程中一个非常重要的概念,尤其是在分布式环境和持久化存储中。让我们深入探讨一下Java...
### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。Java作为一种广泛应用的编程语言,提供了强大的内置支持来实现序列化...
Java序列化面试题(10题) 在 Java 中,序列化是一种用于处理对象流的机制,它可以将对象的内容进行流化,使其可以被读写和传输。下面是 10 个与 Java 序列化相关的面试题目: 1. 什么是 Java 序列化,如何实现 ...
Java序列化是Java平台中的一种标准机制,允许对象的状态被保存到磁盘或者在网络中进行传输,以便在后续的时间或地点恢复这些对象。这个过程包括两个主要操作:序列化(将对象转换为字节流)和反序列化(将字节流恢复...
Java序列化是Java平台提供的一种持久化对象的机制,它允许我们将对象的状态转换为字节流,以便存储或在网络上传输。在这个特定的场景中,我们关注的是如何使用Java序列化来多次追加对象到一个TXT文件,而不是覆盖...
### Java序列化与反序列化详解 #### 一、Java序列化概述 Java序列化(Serialization)是一项重要的功能,它可以将对象的状态转化为一系列字节,从而实现对象的持久化存储或在网络上传输。序列化机制使得Java对象...
### Java对象序列化标准知识点详解 #### 一、系统架构概览 **1.1 概览** Java 对象序列化是一种将Java对象的...以上内容涵盖了Java序列化标准的关键知识点,深入了解这些概念有助于更好地理解和应用Java序列化技术。
**FST:快速Java序列化的替代方案** 在Java开发中,序列化是一个常见的需求,它允许将对象的状态转换为字节流,以便于存储或网络传输。标准的Java序列化虽然方便,但在处理大量数据时,性能往往成为瓶颈。这时,FST...
Java序列化漏洞是一种常见的安全问题,它出现在Java应用程序中,当对象被转化为字节流以便在网络间或存储中传输时。这种序列化过程如果处理不当,可能会导致远程代码执行(RCE)、信息泄露或者权限提升等严重后果。...
android(包括java)序列化一个对象传给php去做处理,或是接到php的序列化的对象在java中做处理的工具jar包以及使用方法. 使用方法: byte[] b = null; b = PHPSerializer.serialize(一个对象);//将一个对象序列化后返回...