java的serializable序列化

引言

java序列化就是java类实现Serializable接口,然后利用ObjectOutputStream将java对象写到二进制文件中,然后将该二进制文件通过网络传输到其它java虚拟机环境中,再利用ObjectInputStream将该二进制文件还原为java对象。本质上是将java虚拟机中生成的对象转移到其它java虚拟机中使用。

前提条件

情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。那么要想序列化和反序列化成功需要具备以下条件:
1.A端和B端都要有C类,而且类路径和功能代码一致。
2.C类实现Serailizable接口
3.C类的序列化id一致。

序列化id的问题

java序列化机制是通过在运行时判断类的序列化id(serialVersionUID)来验证版本一致性的。在进行反序列化时,jvm会把传来的字节流中的serialVersionUID与本地相应java类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不致的异常(InvalidClassException )
1.不显示设置id
jdk文档中有解释,建议我们显示声明,因为如果不声明,Java序列化机制会根据编译的class自动生成一个serialVersionUID,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID,这样的话很容易导致序列化版本不一致的异常。
2.显示设置id
在eclipse中显示设置序列化id有两种方式:
1.add default serial version ID:
生成1L,对于这一种,需要了解哪些情况是兼容的,哪些是不兼容的,在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。1》2》3
2.add generated serial version ID:
生成一个很大的数,这种方式是根据类的结构产生的hash值,增减属性、方法等,都可能会导致这个值产生变化。如果开发人员认为每次修改类后都需要生成新的版本号,不想向下兼容,操作就是删除原有的SerialVersionUid声明语句,再自动生成一下。

静态化变量序列化

java在序列化时,并不保存静态变量信息,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态。

Transient关键字

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

父类的序列化

情境:一个子类实现了Serializable接口,它的父类没有实现,序列化该子类对象,然后反序列化输出父类定义的变量的数值,该变量数值跟Transient关键字的效果一样,都是初始值。
解决:要想将父类对象的属性也序列化,就需要让父类也实现Serializalbe接口。
如果父类不实现序列化,就需要有默认的无参的构造函数。在父类没有实现Serializable接口时,虚拟机不会序列化父类对象,而一个java对象的构造必须先有父类对象,反序列化时也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。如果你考虑到这种序列化的情况,在父类的无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的是 0,对象型的是 null。
结论:根据以上, Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化。

定制序列化

原理:在序列化过程中,虚拟机会试图调用对象类里面的writeObject(private)和readObject(private)方法,进行用户自定义的序列化和反序列化,如果没有定义该方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,当然用户自定义了writeObject也可以调用defaultWriteObject来处理默认的序列化方法。
关于对象里面的writeObject和readObject必须是private类型的,这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法,因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。
定制序列化的过程有两种试,一种是key,value型,一种是value型。
第一种:
序列化:key是对象中必须存在的字段的名称,value是指定的值。
private void writeObject(ObjectOutputStream oos) {
	try {
		PutField putField = oos.putFields();
		putField.put("name", "bao");
		putField.put("name2", "elva");
		oos.writeFields();
	} catch (IOException e) {
		e.printStackTrace();
	}
}


反序列化:key是写的时候的值,不一定是类中必须存在的
private void readObject(ObjectInputStream ois) {
	try{
		GetField getField = ois.readFields();
		String name = (String)getField.get("name", "");
		System.out.println(name);
		String name2 = (String)getField.get("name2", "");
		System.out.println(name2);
	} catch (IOException e) {
		e.printStackTrace();
	} catch (ClassNotFoundException e) {
		e.printStackTrace();
	}
}

第二种:
序列化:依次写入不同字段的值,这里只有值,没有字段名(key)
private void writeObject(ObjectOutputStream oos) {
		try {
			oos.defaultWriteObject(); // 执行默认的序列化过程
			oos.writeObject("aaaaaaa"); // 添加值aaaaaa
			oos.writeObject("bbbbbbb");// 添加值bbbbbb
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

反序列化:依昭序列化时的顺序和个数获取相应的字段值
private void readObject(ObjectInputStream ois) {
		try{
			ois.defaultReadObject();
			Object obj1 = ois.readObject();
			Object obj2 = ois.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

使用场景:如果序列化的对象数据中,有些数据是敏感的,比如密码字符串等,希望对密码字段在序列化时,进行加密,在反序列化时,对密码字段进行解密,这样一定程序上保证序列化对象的数据安全。

序列化存储规则

原理:当序列化同一个对象多次时,序列化存储的二进制文件中该对象只存一份,剩下的全部存引用,这样可以减少存储空间。如果序列化同一个对象多次,当反序列化时获得的对象是同一个,都指向同一个内在地址。
序列化同一个对象多次时,存储的二进制文件空间并没有增加2倍,而只增加一点点(存储对象地址的那部分空间):
public static void main(String[] args) throws Exception {
		
		FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 126
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 131
		
		oos.close();
		fos.close();
		
	}

反序列化时:
public static void main(String[] args) throws Exception {
		
		FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		SerializableEntity entity = (SerializableEntity)ois.readObject();
		SerializableEntity entity2 = (SerializableEntity)ois.readObject();
		
		System.out.println(entity == entity2); // true
		
		ois.close();
		fis.close();
		
	}


如果序列化多次同一个对象的过程中修改了该对象的某个字段值,那么反序列化时获取的对象的值保持不变:因为序列化多次同一个对象只会序列化一次对象数据,剩下的是引用。
public static void main(String[] args) throws Exception {
		
		FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 126
		entity.setName("bao");
		oos.writeObject(entity);
		oos.flush();
		System.out.println(new File("D:/serializable.txt").length()); // 131
		
		oos.close();
		fos.close();
		
	}

public static void main(String[] args) throws Exception {
		
		FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		SerializableEntity entity = (SerializableEntity)ois.readObject();
		System.out.println(entity.getName()); // elva
		SerializableEntity entity2 = (SerializableEntity)ois.readObject();
		System.out.println(entity.getName()); // elva
		
		System.out.println(entity == entity2); // true
		
		ois.close();
		fis.close();
		
	}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值