Java IO : ObjectInputStream、ObjectOutputStream

Java 对象序列化

java平台允许在内存中创建可复用的java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命生命周期不会比JVM生命周期长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。 Java对象序列化就能够实现该功能。使用Java 对象序列化,在保存对象时,会将其转换为字节序列,这些序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。

序列化缘由

有时候我们想把一个java对象写入到磁盘文件或传输到网络到其他计算机,这时我们就需要去把这个对象转换成字节流,另一端接收字节流将其恢复成java对象。所以就有了java序列化的概念了。

好处

  • 实现了数据的持久化
  • 实现 远程通信

序列化ID

序列化ID 提供两种生成策略: 一是固定的1L,一个是随机生成一个不重复的long类型数据(实际上是使用JDK工具生成) 。
Java 的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致,在进行反序列化时,JVM会把传过来的字节流中的serialVersionUID与本地相应实体(类) 的serialVersionUID进行比较,如果相同,则进行反序列化,否则就会出现序列化版本不一致的异常。

如何实现序列化

  • 实现Serializable 接口即可
    当实现Serializable 接口的实体没有显示定义一个名为serialVersionUID,类型为long变量时,java序列化机制会根据编译的class自动生成一个serialVersionUID做序列化版本比较,这种情况下,只有通一次编译生成的class才会生成相同的serialVersionUID。

实例:

普通对象序列化

public class UserInfo implements Serializable{
	
	private static final long serialVersionUID = 1L;
	private String userName;
	private int age; 	
	private String address;
	public UserInfo(String userName, int age, String address) {
		super();
		System.out.println("有参数的构造器");
		this.userName = userName;
		this.age = age;
		this.address = address;
	}
	..... // get、set 方法省略
}
/**
 * ObjectStream
 * 
 * @author mingx
 *
 */
public class ObjectStream {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		ObjectOutputStream oos = null;
		ObjectInputStream oin = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("E:/javaFile/object.txt"));
			UserInfo userInfo = new UserInfo("KANNO爷", 25, "幽冥谷");
			// 将对象写入输出流
			oos.writeObject(userInfo);
			// 反序列化操作
			oin = new ObjectInputStream(new FileInputStream("E:/javaFile/object.txt"));
			UserInfo user = (UserInfo) oin.readObject();
			System.out.println("名字为:" + user.getUserName() + "\n 年龄为:" + user.getAge() + "\n地址为:" + user.getAddress());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				oos.close();
			}
			if (oin != null) {
				oin.close();
			}
		}
	}
}

序列化: 上面的程序创建ObjectOutputStream,这个输出流建立在一个文件输出流的基础上,使用writeObject() 方法将一个UserInfo对象写入输出流,运行上面程序生成一个object.txt文件,该文件的内容就是UserInfo对象。
反序列化:创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础上,调用readObject() 方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该java对象的类型,将强制转换成其真实类型。

注意:UserInfo类中只定义了一个有参数的构造函数,当我们反序列化读取Java对象时,并没有调用该构造函数,这表明反序列化机制无须通过构造器来初始化Java对象。
如果一个可序列化有多个父类,则该类的所有父类要么是可序列化的,要么有无参数的构造器,否则反序列化时会抛出InvalidClassException异常。

对象引用序列化

如果某个类的属性类型是一个引用类型,那么这个引用类型必须是可序列哈的,否则拥有该类型属性的类是不可序列化的。例如:

public class ObjectReferenceSeriable {
	
	public static void main(String[] args) throws IOException, ClassNotFoundException{
		
		ObjectOutputStream out = null; 
		ObjectInputStream oin = null; 
		try {
			//序列化操作
			out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\teacher.txt"));
			UserInfo userInfo = new UserInfo("龙幽", 25); 
			Teacher t1 = new Teacher("净天教", userInfo); 
			Teacher t2 = new Teacher("蛮族", userInfo);
			// 依次将四个对象写入输出流
			out.writeObject(t1);
			out.writeObject(t2);
			out.writeObject(userInfo);
			out.writeObject(t2);
			
			//反序列化操作
			oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\teacher.txt")); 
			// 依次读取输入流的四个对象
			Teacher T1 = (Teacher) oin.readObject(); 
			Teacher T2 = (Teacher) oin.readObject(); 
			UserInfo user= (UserInfo) oin.readObject(); 
			Teacher T3 = (Teacher) oin.readObject();
			System.out.println("T1的student引用和user是否相同:" + (T1.getStudent() == user));
			System.out.println("T2的student引用和user是否相同:" + (T2.getStudent() == user));
			System.out.println("T2和T3是否同一个对象:" + (T2==T3));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally {
			if(out !=null){
				out.close();
			}
			if(oin !=null){
				oin.close();
			}
		}
		
	}
	public static class Teacher implements Serializable{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		private String name; 
		private UserInfo student;
		
		public Teacher(String name,UserInfo student){
			this.name=name; 
			this.student= student; 
		}
		......  //get、set省略
	}

通过上面后面的程序代码比较,可以看出T2和T3是同一个对象,T1的student引用的、T2的student引用的和user引用变量引用的是同一个对象。说明java序列化的机制如下图:
这里写图片描述

从上图中我们可以看出:当我们多次调用writeObject 输出同一个对象时,程序只有第一次调用writeObject方法才会将该对象转换成字节序列并输出。
但这会照成另外一个问题:当程序序列化一个可变对象时,程序只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,即使后面该对象的属性已改变,改变的属性值也不会被输出,例如:

public class SerializeMutable {
	public static void main(String[] args) throws IOException{
		ObjectOutputStream out = null; 
		ObjectInputStream oin = null; 
		try {
			out = new ObjectOutputStream(new FileOutputStream("E:\\javaFile\\mutable.txt"));
			UserInfo user = new UserInfo("pepper",25); 
			out.writeObject(user);
			user.setUserName("Tony");
			out.writeObject(user);
			
			// 反序列化
			oin = new ObjectInputStream(new FileInputStream("E:\\javaFile\\mutable.txt")); 
			UserInfo u1 = (UserInfo) oin.readObject(); 
			UserInfo u2 = (UserInfo) oin.readObject(); 
			
			System.out.println(u1 == u2);
			System.out.println(u2.getUserName());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} 
		finally {
			if(out !=null){
				out.close();
			}
			if(oin !=null){
				oin.close();
			}
		}
	}
}

运行上面程序,程序比较读取的Java对象完全相同,第二次读取的UserInfo对象的userName 属性依然是pepper,说明被改变的userInfo对象并没有被写入。

**静态变量序列化 **

public class StaticSeriable  implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	//定义初始值为5
	public static int staitcAcitivar = 5; 
	
	public static void main(String[] args){
		
		ObjectOutputStream out;
		try {
			out = new ObjectOutputStream(new FileOutputStream("result.obj"));
			out.writeObject(new StaticSeriable());
			out.close();
			//序列化后修改为10
			StaticSeriable.staitcAcitivar = 10; 
			ObjectInputStream oIn = new ObjectInputStream(new FileInputStream("result.obj")); 
			StaticSeriable t = (StaticSeriable) oIn.readObject(); 
			oIn.close();
			//再读取,通过t.staticActivar打印新值
			System.out.println(t.staitcAcitivar);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} 
		
	}
}

上面程序将对象序列化后,修改静态便令的值,再将对象从序列化对象读取出来,打印对象的静态变量值,是10而不是5,这是为什么呢?这是因为序列化时,并不保存静态变量,而是保存对象的状态,静态变量属于类的状态。

发布了181 篇原创文章 · 获赞 405 · 访问量 34万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章