Java 对象序列化学习笔记

1. 什么是Java对象序列化?

Java的对象序列化是将那些实现了Serializable接口的对象转化成一个字节序列,并能够在以后将这些字节序列完全恢复成原来的对象。简单来说序列化就是将对象转化成字节流,反序列化就是将字节流转化成对象。

对象必须在程序中显示的序列化(serialize)和反序列化(deserialize)。

2. 序列化的作用

序列化的主要用途主要有两个,一个是对象持久化,另一个是跨网络的数据交换、远程过程调用。

3. 基本实现

 要实现Java对象的序列化,只要将类实现Serializable或Externalizable接口即可。采用类实现Serializable接口的序列化很简单,Java自动会将非transient修饰属性序列化到指定文件中去。

一个简单的将对象写入到文件的方法:

 private static void saveObj(Object obj) {
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("obj.out"));
            out.writeObject(obj);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 }

ObjectOutputStream类的writeObject()代码如下:

 public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
}

在继承ObjectOutputStream类的子类中, enableOverride属性为true, 并且会去调用实现的writeObjectOverride()方法. 否则直接调用writeObject0方法, 该方法会先根据obj的Class对象获得一个ObjectStreamClass类实例, 该实例包含了对该obj的描述(包括serialVersionUID, 属性域, 方法等). 根据obj的类型来调用不同的write方法, 当obj是除String, Array, Enum以外的对象时, 会去调用writeOrdinaryObject()方法. 该方法先去调用writeClassDesc()方法, 将ObjectStreamClass对象实例的必要信息写入(这样在从流中反序列化一个对象时, 会根据写入的这些信息实例化出ObjectSteamClass对象)会判断如果obj是Externalizable的子类型, 则调用其obj的writeExternal()和readExternal()方法, 否则调用自身的writeSerialData()方法, 在该方法里会去判断obj是否有writeObject()方法, 如果有, 则会直接调用该方法, 否则调用自身的defaultWriteFields()方法.

调用ObjectInputStream类的readObject()方法和写入过程相反, 先根据其classDesc字段信息, 实例化ObjectStreamClass对象, 然后再实例化特定的对象.

4. 部分属性序列化

如果只想将部分属性进行序列化,可以采用如下几种方法:

     1) 使用transient关键字定义不想被序列化的属性

     2) 在将要被序列化的类中添加私有的writeObject和readObject方法,这两个方法会被ObjectOutputStream类中的代码通过反射调用

     3) 被序列化类实现Externalizable接口,Externalizable 接口继承于Serializable,实现该接口,需要重写readExternal和writeExternal方法。另外,使用 Externalizable 接口进行序列化时,读取对象会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中,这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现 Externalizable 接口的类必须要提供一个无参构造器,且它的访问权限为public。

     4) 序列化并不保存静态变量

5. 父类及对象引用序列化

要想将父类对象也序列化,就需要让父类也实现 Serializable 接口

若一个类的字段有引用对象,那么在序列化该类的时候不仅该类要实现Serializable接口,这个引用类型也要实现Serializable接口。但有时我们并不需要对这个引用类型进行序列化,此时就需要使用transient关键字来修饰该引用类型保证在序列化的过程中跳过该引用类型。

通过序列化操作,可以实现对任何可 Serializable 对象的深度复制(deep copy),这意味着复制的是整个对象的关系网,而不仅仅是基本对象及其引用。

如果父类没有实现Serializable接口,但其子类实现了此接口,那么这个子类是可以序列化的,但是在反序列化的过程中会调用父类的无参构造函数,所以在其直接父类(注意是直接父类)中必须有一个无参的构造函数。

6. 序列化的安全性

服务器端给客户端发送序列化对象数据,序列化二进制格式的数据写在文档中,并且完全可逆。一抓包就能就看到类是什么样子,以及它包含什么内容。如果对象中有一些数据是敏感的,比如密码字符串等,则要对字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。 

比如可以通过使用 writeObject 和 readObject 实现密码加密和签名管理,但其实还有更好的方式。如果需要对整个对象进行加密和签名,最简单的是将它放在一个 javax.crypto.SealedObject 或 java.security.SignedObject 包装器中。两者都是可序列化的,所以将对象包装在 SealedObject 中可以围绕原对象创建一种 “包装盒”。必须有对称密钥才能解密,而且密钥必须单独管理。同样,也可以将 SignedObject 用于数据验证,并且对称密钥也必须单独管理

7. ArrayList 序列化要注意的问题

ArrayList实现了java.io.Serializable接口,但是其 elementData 是 transient 的,但是 ArrayList 是通过数组实现的,数组 elementData 用来保存列表中的元素。通过该属性的声明方式知道该数据无法通过序列化持久化。

但是如果实际测试,就会发现,ArrayList 能被完整的序列化,原因是在writeObject 和 readObject方法中进行了序列化的实现。

这样设计的原因是因为 ArrayList 是动态数组,如果数组自动增长长度设为 2000,而实际只放了一个元素,那就会序列化 1999 个 null 元素,为了保证在序列化的时候不会将这么多 null 元素序列化,ArrayList 把元素数组设置为transient,但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化,所以,通过重写 writeObject 和 readObject 方法把其中的元素保留下来,具体做法是:

writeObject方法把elementData数组中的元素遍历到ObjectOutputStream

readObject方法从ObjectInputStream中读出对象并保存赋值到elementData数组

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值