Java的序列化与反序列化

定义:

序列化是一种对对象进行持久化的手段。网络传输、RMI常用。(序列化就是将对象状态转化成一组字节,在未来再将其重新组成对象)

问题:

1.为什么要有序列化?

2.怎么实现序列化及其机制

3.为什么需要重载serialVersionUID?

4.为什么实现了Serializable接口才能序列化

5.transient的作用

6.自定义序列化的策略

7.自定义的序列化策略是如何被调用的

8.ArratList为例

背景

java允许我们在内存创建可复用的java对象,但这些java对象的生命周期都是受限制于JVM。如果想要在JVM停止运行后仍然能够保存对象,并在将来能够重新读取并使用,那么序列化能够帮助实现。

对象序列化可实现分布式对象:RMI利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

序列化可以递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可进行“深复制”。

极少数情况下才需要定制代码保存或恢复对象状态,不是每个类都可序列化,如涉及线程的类与特定JVM有非常复杂的关系。

如何实现

需实现java.io.Serializable/Externalizable接口以启用序列化功能。Java的序列化机制是通过运行时判断类的seiralVersionUID验证版本一致性。反序列化时,JVM会把传入的字节流中的serialVersionUID与本地相应类的serialVersionUID进行比较。有两种生产方式:默认的1L;根据类名、接口名、成员方法及属性等生成64位的哈希字段。

机制

序列化是将对象写入字节流和从字节流中读取对象。对象状态转换成字节流后,可用java.io包中的各种字节流保存至文件中,管道到另一个线程或通过网络连接将对象数据发送到另一主机。在RMI、Socket、JMS、EJB都有应用。

序列化,将数据分解成字节流,用于传输。反序列化,打开字节流重构对象。对象序列化需要将基本数据类型转成字节,还需要恢复数据。恢复数据则要求有对应的对象实例。ObjectOutputStream序列化过程与字节流连接,包括对象类型和版本信息。ObjectInputStream反序列化时,JVM用头信息生成对象实例,然后将字节流数据复制到对象数据成员中。

java.io.ObjectOutputStream代表对象输出流,扩展自DataOutput接口。writeOject(Object obj)可将指定的obj序列化,写入一个目标输出流中。若对象包含其它引用则writeObject()递归序列化。每个ObjectOutputStream都会维护一个需要序列化的对象引用列表,防止一个对象多个拷贝(如交叉引用)

java.io.ObjectInputStream代表对象输入流,扩展自DataOutput接口。readObject()从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并返回。每次调用readObject()都会返回流中下一个对象。对象字节流不传输类的字节码,而是类名及其签名。readObject收到时JVM装入头中指定的类。找不到则报异常ClassNotFoundException。如果传输对象数据和字节码,可用RMI框架。

serialVersionUID

Externalizable继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,仅实现Serializable接口的类可默认序列化方式。实现了Serializable接口的类都有一个可声明表示序列化版本的静态变量:privatestatic final long serialVersionUID;序列化运行时使用该静态变量(版本号)与每个可序列化类相关联,该序列号在反序列化过程中会验证版本号,不同则反序列化时会抛异常:InvalidClassException。若可序列化类没有显示声明,则序列化运行时会基于类的信息计算值,建议显示声明,因为各个编译器的实现和对类信息的敏感性都会影响计算的结果。private修饰是作为继承成员变量没有用处。数组不能声明一个明确的值,因此他们总是具有默认的计算值,但数组没有匹配Serializable值的要求。

显示定义的两种用途:

1.某些场合希望对不同版本的类进行兼容;某些场合不希望不同版本的类兼容。

2.序列化了一个类实例后,增删改一个字段,不设置serialVersionUID,所做的任何修改都无法反序列化旧有的实例并抛出异常,如果设置了UID,增改的字段设为初始化值,删除则不设置

注意:

1)序列化只是保存对象状态,不管方法;

2)父类实现序列化,子类自动实现,不需显示实现;

3)当对象的实例变量引用其他对象,序列化时则会把引用对象也序列化;

定制序列化

通常可自动完成,有时需要对这个过程进行控制,Java可以将类声明为serializable,但可手工控制声明类为static或transient的数据成员

public class SimpleSerializableClass implements Serializable{
    String sToday=”Today:”;
    transient Date dtToday=new Date();
}

声明为transient是表示我们负责将变元序列化,序列化也无法将其加进对象字节流中,反序列化时重建的数据成员没有数据。类需要用writeObject()和readObject()方法处理这些数据成员,需要注意写入顺序读取这些数据成员。

//重写writeObject()方法以便处理transient的成员。
public void writeObject(ObjectOutputStream outputStream) throws IOException{
    outputStream.defaultWriteObject();//使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
    outputStream.writeObject(oSocket.getInetAddress());
    outputStream.writeInt(oSocket.getPort());
}

//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
    inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
    InetAddress oAddress=(InetAddress)inputStream.readObject();
    int iPort =inputStream.readInt();
    oSocket = new Socket(oAddress,iPort);
    iID=getID();
    dtToday =new Date();
}


完全定制序列化

需实现Externalizable接口,含两个方法:writeExternal()与readExternal()。可利用这两个方法控制对象数据成员如何写入字节流。类实现接口,头会写入字节流,然后类完全负责序列化和恢复。除了头,其他都不是自动序列化。声明类实现此接口会有风险,writeExternal()、readExternal()为public,恶意类可用这两方法读取或写入数据。

以ArrayList为例

。。。


最后:

1.实现了Serializable接口,可以被序列化

2.通过ObjectOutputStream(写入文件)和ObjectInputStream(写出文件)对对象进行序列化和反序列化

3.虚拟机是否反序列化,不仅取决于类路径和功能代码是否一致,最重要的一点是两个类的序列化ID是否一致(serialVersionUID)

4.序列化保存对象的状态,不保存静态变量

5.若序列化父类,则父类也需实现Serializable接口

6.Transient关键字是控制变量的序列化,变量声明前加上该关键字则可以阻止该变量序列化到文件中,在被反序列化后,transient变得的值被设为初始值(0、null)

7.服务器端给客户发送序列化对象数据,对象中有一些数据是敏感的,如密码字符串等,希望对该密码字段在序列化时进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才能对密码进行读取,这样可以一定程度包装序列化对象的数据安全。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值