JAVA序列化

一、什么是序列化和反序列化?

序列化:把对象转换为字节序列的过程

反序列化:把字节序列恢复为对象的过程

用途:

  • 把对象的字节序列永久保存在硬盘上,通常存放在一个文件中。  对象序列化机制允许把内存中的JAVA对象转换成跟平台无关的二进制流,从而允许将这种二进制流持久地保存在磁盘上。在很多应用中,需要将某些对象进行序列化,让它离开内存空间,入住物理硬盘,一遍长期保存,最常见的web服务器的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中
  • 在网络上传送对象的字节序列。 发送方需要将这个java对象转换为字节序列,才能在网络上传送,接受方则需要把字节序列再恢复为java对象

二、JDK类库中的序列化API

 

 

对象序列化步骤:

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

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

对象反序列化步骤:

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

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

 

示例:

需要序列化处理的类:

package com.cn;

import java.io.Serializable;
import java.util.Date;

public class Example implements Serializable{

	private static final long serialVersionUID = -2317759825949525703L;
	
	private int id;
	
	private String name;
	
	private Date date;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}
	

}

 

序列化、反序列化操作:

package com.cn;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.MessageFormat;
import java.util.Date;

public class TestSerialize {

	public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException, IOException {
		serializeExample();
		Example ex = deSerializeExample();
		System.out.println(MessageFormat.format("id={0},name={1},date={2}", ex.getId(),ex.getName(),ex.getDate()));
	}
	
	
	private static void serializeExample() throws FileNotFoundException, IOException {
		Example ex = new Example();
		ex.setId(1111);
		ex.setName("测试");
		ex.setDate(new Date());
		
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
		
		oos.writeObject(ex);
		
		System.out.println("对象序列化--------------");
		
		oos.close();
	}
	
	private static Example deSerializeExample() throws FileNotFoundException, IOException, ClassNotFoundException {
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
		
		Example ex = (Example) ois.readObject();
		
		System.out.println("对象反序列化-------------");
		
		return ex;
	}

}

运行结果:

对象序列化--------------
对象反序列化-------------
id=1,111,name=测试,date=19-2-28 下午6:27

E盘中会生成aaaa.txt文件

注:如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取 

三、对象引用的序列化

1.对于对象中成员变量若是引用类型,会有什么不同?

  这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化,static修饰的变量不能序列化,transient修饰的变量不再被序列化,Thread不会被序列化

2. 假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!

为了避免这种情况,JAVA的序列化机制采用了一种特殊的算法:

1、所有保存到磁盘中的对象都有一个序列化编号。

2、当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化,系统才会将该对象转换成字节序列并输出。

3、如果对象已经被序列化,程序将直接输出一个序列化编号,而不是重新序列化。

 

四、自定义序列化

         自定义序列化是由ObjectInput/OutputStream在序列化反序列化时候通过反射检查该类是否存在以下方法(0个或多个),执行顺序从上到下,序列化调用1/2,反序列化调用3、4;当某个字段被声明为transient后,默认序列化机制就会忽略该字段

         1、Object writeReplace() throws ObjectStreamException;    可以通过此方法修改序列化的对象

         2、void writeObject(java.io.ObjectOutputStream out) throws IOException;     方法中调用defaultWriteObject() 使用writeObject的默认的序列化方式,除此之外可以加上一些其他的操作,如添加额外的序列化对象到输出:out.writeObject("XX")

         3、void readObject(java.io.ObjectInputStream in) throws Exception;    方法中调用defaultReadObject()使用readObject默认的反序列化方式,除此之外可以加上一些其他的操作,如读入额外的序列化对象到输入:in.readObject()

         4、Object readResolve() throws ObjectStreamException;     可以通过此方法修改返回的对象

 

运用场景

对敏感字段加密,如密码字符等,希望对该密码字段进行序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

示例一:对密码字段加密

package com.cn.prototype.serialize.self;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectOutputStream.PutField;
import java.io.Serializable;

public class Login implements Serializable {

	private static final long serialVersionUID = 4651265039305098539L;
	
	private String name;
	
	private String password;
	
	public Login(String name, String password) {
		this.name = name;
		this.password = password;
	}
	
	
	private void writeObject(ObjectOutputStream out) throws IOException {
		PutField putFields = out.putFields();
		System.out.println("原密码:" + password);
		password = "encryption";
		putFields.put("name", name);
		putFields.put("password", password); //模拟加密
		System.out.println("加密后的密码" + password);
		out.writeFields();
	}
	
	
	private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
		GetField readFields = in.readFields();
		Object object = readFields.get("password", "");
		System.out.println("要解密的字符串:" + object.toString());
		name = (String) readFields.get("name", "");
		password = "123456";
	}
	
	

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
	
}
package com.cn.prototype.serialize.self;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.MessageFormat;

public class Test {

	public static void main(String[] args) throws Exception {
		Login login = new Login("小明","123456");
		
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
		oos.writeObject(login);
		oos.close();
		
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
		Login l = (Login) ois.readObject();
		ois.close();
		
		System.out.println(MessageFormat.format("name={0},password={1}", l.getName(),l.getPassword()));

	}

}

结果:

原密码:123456
加密后的密码encryption
要解密的字符串:encryption
name=小明,password=123456  

 

      示例二:单例模式的类实现序列化接口,若使用默认的序列化策略,则在反序列化返回的对象不符合单例模式(反射创建了新的对象),可以通过修改序列化的readResolve来实现自定义序列化返回结果来实现单例对象唯一(相当于1,2,3方法对4的结果毫无作用)。

package com.cn.prototype.serialize.self;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

public class Singleton implements Serializable {

	private static final long serialVersionUID = -1523101416687991284L;

	private String name;
	
	private static Singleton singleton = null;
	
	private Singleton(String name) {
		this.name = name;
	};
	
	public static Singleton getInstance() {
		if(singleton == null) 
			singleton = new Singleton("TEST");
			
		return singleton;
	}
	
	
	private Object writeReplace() {
		 System.out.println("1 write replace start");
		 return this;
	}
	
	private void writeObject(ObjectOutputStream out) throws IOException {
		System.out.println("2 write object start");
		out.defaultWriteObject();
	}
	
	
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        System.out.println("3 read object start");
        in.defaultReadObject();
    }

    private Object readResolve() throws ObjectStreamException {
        System.out.println("4 read resolve start");
        return Singleton.getInstance();//不管序列化的操作是什么,返回的都是本地的单例对象
    }
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	
	public static void main(String[] args) throws Exception{
		
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:/aaa.txt")));
		oos.writeObject(Singleton.getInstance());
		oos.close();
		
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
		Singleton singleton = (Singleton) ois.readObject();
		
		ois = new ObjectInputStream(new FileInputStream(new File("E:/aaa.txt")));
		Singleton singleton1 = (Singleton) ois.readObject();
		ois.close();
		

        System.out.println("sington person hashcode:" + singleton.hashCode());
        System.out.println("sington person1 hashcode:" + singleton1.hashCode());
        System.out.println("singleton getInstance hashcode:" + Singleton.getInstance().hashCode());
        System.out.println("singleton person equals:" + (singleton == Singleton.getInstance()));
        System.out.println("person equals1:" + (singleton1 == singleton));
	}
	
}

结果:

1 write replace start
2 write object start
3 read object start
4 read resolve start
3 read object start
4 read resolve start
sington person hashcode:1442407170
sington person1 hashcode:1442407170
singleton getInstance hashcode:1442407170
singleton person equals:true
person equals1:true

 

一些知识点总结:

序列化问题

1、静态变量不会被序列化。

2、子类序列化时:

     如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错;

     如果父类没有实现Serializable接口,提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。

     如果父类实现了Serializable接口,则父类和子类都可以序列化。

transient使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值