zoukankan      html  css  js  c++  java
  • 第三节:控制序列化和反序列化

    使用SerializableAttribute这个定制attribute应用于一个类型时,所有实例字段都会被序列化。然而,类型可能定义了一些不应序列化的实例字段。一般情况下,有两个原因促使我们不想对类型的部分实例字段进行序列化。

    1. 字段含有反序列化变得无效的信息。例如,假定一个对象包含到一个Window内核对象(如文件、进程、线程、互斥体、事件、信号量等)的句柄,那么在序列化到另一个进程或另一台机器之后,就会失去意义。因为Windows内核对象时跟进程相关的值。
    2. 字段含有很容易计算的信息。在这种情况下,要选出哪些无需序列化的字段,减少需要传输的数据,从而增强应用程序的性能。

    以下代码使用了NonSerializableAttribute定制的attribute来指明类型的哪些字段不应序列化。注意,这个attribute也是在System命名空间中定义的。

    [Serializable]
        internal class Circle
        {
            private double m_radius;
            [NonSerialized]
            private double m_area;
    
            public Circle(double radius)
            {
                m_radius = radius;
                m_area = Math.PI * m_radius * m_radius;
    
            }
        }

    在上述代码中,Circle的对象可以序列化。然而,格式化器只会序列化对象的m_radius字段的值。m_area字段中的值不会序列化,因为字段已应用了NonSerializableAttribute。注意,这个attribute只能应用于类型中的一个字段,而且会被派生类继承。当然,可以向一个类型中的多个字段应用NonSerializableAttribute。

    假定我们的代码像下面这样构造了一个Circle对象:

    Circle c=new Circle(10);

    在内部,m_area字段会设置成一个约为314.159的值。这个对象序列化时,只有m_radius字段的值才会写入流。这正是我们希望的,但是当流反序列化一个Circle对象时,就会遇到一个问题。反序列化时,Circle对象的m_radius字段会被设为10,但它的m_area字段会被初始化成0,而不是314.159!

    以下代码演示了如何修改Circle类型来修正这个问题:

    [Serializable]
        internal class Circle
        {
            private double m_radius;
            [NonSerialized]
            private double m_area;
    
            public Circle(double radius)
            {
                m_radius = radius;
                m_area = Math.PI * m_radius * m_radius;
    
            }
            [OnDeserialized]
            private void OnDeserialized(StreamingContext context)
            {
                m_area = Math.PI * m_radius * m_radius;
            }
        }

    在修改过的Circle中,包含一个用System.Runtime.Serialization.OnDeserializedAttribute定制的attribute进行了标记的方法。每次反序列化一个类型的实例,格式化器都会检查类型中是否定义了一个应用了该attribute的方法。如果是,就调用该方法。调用这个方法时,所有可序列化的字段都会被正确设置。在方法中,可能需要访问这些字段来执行一些额外的工作,从而确保对象的完全发序列化。

    在上述Circle修改版本中,我调用OnDeserialized方法,使用m_radius字段来计算圆的面积,并将结果放到m_area字段中。这样一来,m_area就有了我们希望的值.

    除了OnDeserializedAttribute这个定制attrubite,System.Runtime.Serialization命名空间中还定义了OnSerializingAttribute,OnSerializingAttribute和OnDeserializedAttribute这些定制attribute.可将他们应用于类型中定义的方法,对反序列化和序列化过程进行更多的控制。在下面这个类中,这些attribute被应用于不同的方法:

    [Serializable]
        public class MyType
        {
            Int32 x, y;
            [NonSerialized]
            Int32 sum;
            public MyType(Int32 x, Int32 y)
            {
                this.x = x;
                this.y = y;
                sum = x + y;
            }
            [OnDeserializing]
            private void OnDeserializing(StreamingContext context)
            {
                //示例:在这个类型的新版本中,为字段设置默认值
            }
            [OnDeserialized]
            private void OnDeserialized(StreamingContext context)
            {
                //示例:根据字段初始化瞬时状态
                sum = x + y;
            }
            [OnSerializing]
            private void OnSerializing(StreamingContext context)
            { 
                //示例:在序列化钱修改任何需要修改的状态
            }
            [OnSerialized]
            private void OnSerialized(StreamingContext context)
            {
                //示例:在序列化后,恢复任何需要恢复的状态
            }
    
        }

    使用这4个属性中的任何一个时,你定义的方法必须获取一个StreamingContext参数并返回void.方法名可以使你希望的任何名称。另外,应该将方法声明为private,以避免他被普通的代码调用;格式化器运行时有充足的的权限,所以能调用私有方法。

    如果序列化一个类型的实例,在类型中添加一个新字段,然后试图反序列化不包含新字段的对象,格式化器会抛出一个SerializationException异常,并显示一条消息告诉你流中要反序列化的数据包含错误的成员数目。这非常不利于版本控制,因为我们经常要在一个类型的版本中添加字段。幸好,这是可以利用System.Runtime.Serialization.OptionalFieldAttribute的帮助。

    类型中新增的每个字段都应用一个OptionalFieldAttribute,然后,当格式化器遇到该attribute应用于一个字段时,就不会因为流中的数据不包含这个字段而抛出SerializationException。

    注意  序列化一组对象时,格式化器首先调用对象标记了OnSerializing attribute的所有方法。接着,它序列化所有对象的字段。最后,调用对象标记了OnSerialized的所有方法。类似的,反序列化一组对象时,格式化器调用对象标记了OnDeserializing所有方法,然后,它反序列对象的所有字段。最后,它调用对象的标记了OnDeserialized的所有方法。

    还要注意,在反序列化期间,当一个格式化器看到一个类型提供的一个方法标记了OnDeserialized时,格式化器会将这个对象的引用添加到一个内部列表中。所有对象都反序列化之后,格式化器以相反的方向遍历这个列别,调用每个对象的OnDeserialized方法。调用这个方法后,所有可序列化的字段都会被正确设置,可访问这些字段来执行任何必要的、进一步的工作,以便将对象完整的反序列化。之所以要以相反的顺序调用这些方法,因为允许内层对象先于外层对象结束反序列化。

    例如,假定一个集合对象内部用一个哈希表维护他的数据项列表。集合对象类型可实现一个标记了OnDeserialized方法。即使集合对象先反序列化,它的OnDeserialized方法也会最后调用(在调用完它的数据项的所有OnDeserialized方法之后)。这样一来,所有数据项都反序列化后,他们的所有字段都能得到正确的初始化,以便计算出一个好的哈希码值。然后,集合对象创建它的内部哈希桶、并利用数据项的哈希码将数据项放到桶中。

  • 相关阅读:
    Java验证码
    Java内存泄漏问题
    Java常见异常总结
    Java编码与乱码问题
    Spring:源码解读Spring IOC原理
    Quartz 定时邮件发送多个备份文件
    spring-boot 多线程
    java并发基础
    Java多线程中wait语句的具体使用技巧
    多线程的死锁
  • 原文地址:https://www.cnblogs.com/bingbinggui/p/4621216.html
Copyright © 2011-2022 走看看