`
喻红叶
  • 浏览: 40810 次
  • 性别: Icon_minigender_1
  • 来自: 哈尔滨
社区版块
存档分类
最新评论

Java序列化

 
阅读更多

在一些情况下,如果对象能够在程序不运行的情况下仍能存在并保存其信息,这样在下次运行程序时,该对象将被重建,并且拥有的信息与程序在上次运行时它所拥有的信息相同,这将非常有用。“将一个对象编码成一个字节流”,称为对象序列化,相反的处理过程称为反序列化。一旦对象被序列化后,它的编码就可以从一台正在运行的虚拟机被传递到令一台虚拟机上,或者被存储到磁盘上,供以后反复使用。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 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...

    java序列化(Serializable)的作用和反序列化.doc

    ### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...

    Protocol Buffer序列化对比Java序列化.

    【Protocol Buffer序列化对比Java序列化】 Protocol Buffer(简称PB)是Google开发的一种高效的数据序列化协议,而Java序列化是Java平台内置的一种序列化机制。两者的主要目标都是将对象转化为字节数组,便于在网络...

    java 序列化时排除指定属性

    Java序列化是Java平台提供的一种持久化机制,它允许我们将一个Java对象转换为字节流,以便存储到磁盘上,或者通过网络进行传输。这使得我们可以保存和恢复对象的状态。实现序列化的类需要实现`Serializable`接口,...

    java序列化全解

    Java序列化是Java平台中的一种核心机制,它允许对象的状态被转换成字节流,以便存储到磁盘、数据库,或者在网络中进行传输。这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关...

    java序列化(Serializable)的作用和反序列化

    ### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...

    java 序列化代码示例

    Java序列化是Java平台中的一种标准机制,它允许将对象的状态转换为字节流,以便存储、传输或恢复。在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将...

    Java序列化Jar包

    Java序列化是Java平台中的一项重要技术,它允许对象的状态被转换为字节流,以便存储或通过网络进行传输。这种技术在分布式系统、持久化存储以及数据交换等场景中非常常见。本资源包含了三个流行的Java序列化框架:...

    Java序列化的机制和原理

    Java序列化是Java平台提供的一种将对象转换为字节流,以便存储、在网络上传输或者在后续时间重新创建相同对象的机制。这是Java编程中一个非常重要的概念,尤其是在分布式环境和持久化存储中。让我们深入探讨一下Java...

    java序列化原理与算法

    ### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。Java作为一种广泛应用的编程语言,提供了强大的内置支持来实现序列化...

    07-Java序列化面试题(10题)-新增.pdf

    Java序列化面试题(10题) 在 Java 中,序列化是一种用于处理对象流的机制,它可以将对象的内容进行流化,使其可以被读写和传输。下面是 10 个与 Java 序列化相关的面试题目: 1. 什么是 Java 序列化,如何实现 ...

    java序列化实现演示

    Java序列化是Java平台中的一种标准机制,允许对象的状态被保存到磁盘或者在网络中进行传输,以便在后续的时间或地点恢复这些对象。这个过程包括两个主要操作:序列化(将对象转换为字节流)和反序列化(将字节流恢复...

    Java序列化多次追加到txt以及从txt反序列化

    Java序列化是Java平台提供的一种持久化对象的机制,它允许我们将对象的状态转换为字节流,以便存储或在网络上传输。在这个特定的场景中,我们关注的是如何使用Java序列化来多次追加对象到一个TXT文件,而不是覆盖...

    java序列化和反序列化

    ### Java序列化与反序列化详解 #### 一、Java序列化概述 Java序列化(Serialization)是一项重要的功能,它可以将对象的状态转化为一系列字节,从而实现对象的持久化存储或在网络上传输。序列化机制使得Java对象...

    Java对象序列化标准最新版

    ### Java对象序列化标准知识点详解 #### 一、系统架构概览 **1.1 概览** Java 对象序列化是一种将Java对象的...以上内容涵盖了Java序列化标准的关键知识点,深入了解这些概念有助于更好地理解和应用Java序列化技术。

    E043-服务漏洞利用及加固-利用Java序列化漏洞进行渗透测试.pdf

    Java序列化漏洞是一种常见的安全问题,它出现在Java应用程序中,当对象被转化为字节流以便在网络间或存储中传输时。这种序列化过程如果处理不当,可能会导致远程代码执行(RCE)、信息泄露或者权限提升等严重后果。...

    java序列化对象传给php

    android(包括java)序列化一个对象传给php去做处理,或是接到php的序列化的对象在java中做处理的工具jar包以及使用方法. 使用方法: byte[] b = null; b = PHPSerializer.serialize(一个对象);//将一个对象序列化后返回...

    java序列化之protobuf

    Java序列化是将Java对象转换为字节流的过程,以便可以在网络上传输或存储在磁盘上。这使得数据能够跨不同的系统平台进行传输和持久化。Protocol Buffers(protobuf)是Google推出的一种高效、跨平台的数据序列化协议...

Global site tag (gtag.js) - Google Analytics