今天锤一锤这个序列化。
序列化,名字听着牛逼,网上搜罗得知,是拿来干持久化的,啥是持久化,就是比如你一个对象创建了出来,但是虚拟机一关掉,这个对象又没了,但是我们还是想保存这个对象的属性,所以就需要将这个对象的属性保存到本地,怎么保存呢?就是序列化了。
序列化可以把对象的状态信息转为字节数组,然后再需要用到的地方又可以反序列化将其转回对象。
下面实战:
首先,一个类要想能序列化,就得先实现序列化接口,这样才能使用里面的序列化方法:
这里把User类做成可序列化的类,接着就是序列化的具体实现了
首先我们新建一个User对象,给他赋上属性:
接着就是通过使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写:
这里oos是将对象写入流中,再通过FileOutputStream写入文件中,最后要记得关闭流,这里虽然只关闭了ObjectOutputStream,实际上也把FileOutputStream关闭了,后者的关闭在前者的close方法中已经释放了。
序列化写入之后,我们将其取出:
流的转换与写入无异,在读取的时候使用的是readObject方法,赋值给对象的时候需要转换一下具体的对象类型,最后打印对象可以发现数据再次读出来了。
序列化是用来保存对象状态的,自然而然静态属性是不能序列化的,不信试一试:
第一句话提供了一个serialVersionUID,虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L),在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功,原因便是这个serialVersionUID不一致。
接着便是一个静态变量,下面与上面所说的序列化对象没什么差异,在完成对象的序列化写入后,对这个静态属性进行了修改变成了10,接着再从文件中反序列化取出对象写入staticSerial,输出结果,可以看到静态属性是10。
再说个情况,父类的序列化:
一个已经实现序列化接口的子类,如果他的父类没有实现序列化接口,那么父类的的属性会根据他的构造方法来创建,且该构造方法必须是无参的构造方法。虽然说父类没有实现序列化接口,虚拟机是不会序列化父对象的,但是Java对象的构造是从父对象到子对象的,如果父类的无参构造方法没有对值进行修改的话,那么父类的属性都是默认的值(如int为0,string为null)。
这种情况让我们想到transient这个描述符,该描述符是将一个属性描述为不可序列化的属性,上面讲的父类如果没有实例化接口的话属性是不会序列化的,这样的话我们可以想到如果把属性提到父级当中并且不实现序列化,使得字段不被序列化。
如图所示,attr1,attr2,attr3以及attr5都不会被实例化,就算我们再有一个别的类,父类的属性始终是不会被序列化的。
还有一个就是可以在序列化的过程中对序列化信息加密,主要方法是在实现序列化接口的类中实现writeObject和readObject方法,因为在实现序列化的过程中,虚拟机会首先在类中尝试调用自定义实现的writeObject和readObject:
这样的话,我们就可以在自定义的方法中写入自己的加密过程:
再序列化读写过后可以发现再次读出来的对象password已经改变了。
序列化还有他自己独特的存储规则,下面以一个demo举例:
我们先往本地序列换写入一个对象看看文件的大小
存了个user对象,大小是106,那两个岂不是212了?再试一下:
结果第一个输出106,第二个输出111,发现只大了5,这是因为JAVA序列化机制发现写入文件的为同一个对象的时候,不会再次对对象内容存储进去,而是存储一份对象的引用,加上一些控制的信息,这些就是多出来的5了。
如果在写入第一份对象后,对对象属性进行修改,再次写入,序列化机制会认为你存储了同一个对象,所以保存的只有引用,对象的状态信息仍然是第一次写入该对象的属性: