黑马程序员-序列化与反序列化

本文详细介绍了Java中对象序列化的基本概念、应用场景及其实现方式。重点讲解了如何使用ObjectOutputStream和ObjectInputStream进行对象的序列化与反序列化操作。

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

  把Java对象转换为字节序列的过程称为对象的序列化。

  把字节序列恢复为Java对象的过程称为对象的反序列化。

  对象的序列化主要有两种用途:

  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

  2) 在网络上传送对象的字节序列。

  一. JDK类库中的序列化API

  java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

  java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。、

  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。

  对象序列化包括如下步骤:

  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;

  2) 通过对象输出流的writeObject()方法写对象。

  对象反序列化的步骤如下:

  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

  2) 通过对象输入流的readObject()方法读取对象。

 二.实现Serializable接口

  ObjectOutputStream只能对Serializable接口的类的对象进行序列化。默认情况下,ObjectOutputStream按照默认方式序列化,这种序列化方式仅仅对对象的非transient的实例变量进行序列化,而不会序列化对象的transient的实例变量,也不会序列化静态变量。

  当ObjectOutputStream按照默认方式反序列化时,具有如下特点:

  1) 如果在内存中对象所属的类还没有被加载,那么会先加载并初始化这个类。如果在classpath中不存在相应的类文件,那么会抛出ClassNotFoundException;

  2) 在反序列化时不会调用类的任何构造方法。

    三. 可序列化类的不同版本的序列化兼容性

  凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

       serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。

  类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

  1) 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

  2) 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

 

package com.itcast.test1;
import java.util.*;
import java.io.*;
public class SerializableTest {
	/**
	 * 对象序列化与反序列化
	 * 进行对象序列化主要目的是为了保存对象的状态(成员变量)。
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		List<Ball> list = new ArrayList<Ball>();
		Ball ball1 = new Ball("basketball",1);
		Ball ball2 = new Ball("football",2);
		Ball ball3 = new Ball("badminton",3);
		list.add(ball1);
		list.add(ball2);
		list.add(ball3);
		try {
			serialize(list);
			deserialize("obj.ser");
		} catch(Exception e) {
			e.printStackTrace();
		}	
	}
	/**
	 * 进行序列化主要用到的流是FileOutputStream和ObjectOutputStream。
	 * FileOutputStream主要用于连接磁盘文件,并把字节写出到该磁盘文件;
	 * @param list
	 * @throws IOException
	 */
	public static void serialize(List<Ball> list) throws IOException {
		FileOutputStream fos = new FileOutputStream("obj.ser");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		for(Ball ball : list) {
			oos.writeObject(ball);
		}
	}
	/**
	 * 保存状态的目的就是为了在未来的某个时候再恢复保存的内容,
	 * 这可以通过反序列化来实现。对象的反序列化过程与序列化正好相反,
	 * 主要用到的两个流是FileInputstream和ObjectInputStream。
	 * 反序列化后得到的对象的顺序与保存时的顺序一致。
	 * @param fileName
	 * @throws Exception
	 */
	public static void deserialize(String fileName) throws Exception {
		FileInputStream fis = new FileInputStream(fileName);
		ObjectInputStream ois = new ObjectInputStream(fis);
		while (true) {
			try {
					Ball ball = (Ball) ois.readObject();
					System.out.println(ball.toString());
			} catch (EOFException e) {
				System.out.println("文件已到末尾");
				break;
			}
		}
	}
}

/**
 * 补充一:上面我们举得例子很简单,要保存的成员变量要么是基本类型的要么是String类型的。
 * 但有时成员变量有可能是引用类型的,这是的情况会复杂一点。那就是当要对某对象进行序列化时,
 * 该对象中的引用变量所引用的对象也会被同时序列化,并且该对象中如果也有引用变量的话则该对象
 * 也将被序列化。总结说来就是在序列化的时候,对象中的所有引用变量所对应的对象将会被同时序列化。
 * 这意味着,引用变量类型也都要实现Serializable接口。当然其他对象的序列化都是自动进行的。
 * 所以我们只要保证里面的引用类型是都实现Serializable接口就行了,如果没有的话,会在编译时抛出异常。
 * 如果序列化的对象中包含没有实现Serializable的成员变量的话,这时可以使用transient关键字,
 * 让序列化的时候跳过该成员变量。使用关键字transient可以让你在序列化的时候自动跳过transient所修饰的成员变量,
 * 在反序列化时这些变量会恢复到默认值。
 * 补充二:如果某类实现了Serializable接口的话,其子类会自动编程可序列化的,这个好理解,继承嘛。
 * 补充三:在反序列化的时候,并不会调用对象的构造器,这也好理解,如果调用了构造器的话,对象的状态不就又重新初始化了吗。
 * 补充四:我们说到对象序列化的是为了保存对象的状态,即对象的成员变量,所以静态变量不会被序列化。
 */
package com.itcast.test1;

import java.io.Serializable;

/**
 * javabean多用于封装属性,而很少具备某些特定的方法功能。
 * 试想,javabean的功能就是传递数据,那么类权限为共有也
 * 就不足为奇了。下面是标准javabean要遵守的一些规范:
 *1. 类访问权限为公有
 *2. 所有属性为私有
 *3. 每个字段对外提供setter方法和getter方法
 *4. 具备一个无参的构造方法
 */
public class Ball implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 实现序列化
	 * 要将某类的对象序列化,则该类必须实现Serializable接口,该接口
	 * 仅是一个标志,告诉JVM该类的对象可以被序列化。如果某类未实现Serializable接口,
	 * 则该类对象不能实现序列化。
	 */
	private String ballName;
	private int serialNumber;
	public Ball(String ballName, int serialNumber) {
		super();
		this.ballName = ballName;
		this.serialNumber = serialNumber;
	}
	public String getBallName() {
		return ballName;
	}
	public void setBallName(String ballName) {
		this.ballName = ballName;
	}
	public int getSerialNumber() {
		return serialNumber;
	}
	public void setSerialNumber(int serialNumber) {
		this.serialNumber = serialNumber;
	}
	@Override
	public String toString() {
		return "Ball [ballName=" + ballName + ", serialNumber=" + serialNumber
				+ "]";
	}	
}


 

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值