• Java 知识点:序列化


    首先明确一点:默认的序列化方法速度很慢,因为需要对整个对象和他的类都进行保存,因此我们建议自定义序列化格式。

    ObjectInputStream和ObjectOutputStream

    用途 ObjectInputStream ObjectOutputStream
    整数 readInt() writeInt(int)
    浮点数 readDouble() writeDouble(double)
    字符串 readUTF() writeUTF(String)
    字节数组 read(byte[] buf, int off,int length) write(byte[])
    对象 readObject() writeObject(Object)

    哪些数据不会被序列化

    1. 被标记为 transient 的域。
    2. 静态变量。

    查看某个类的SerialVersionUID

    如果类A实现了Serializable接口,则可以使用Java 提供的serialver命令: serialver  A 。

    SerialVersionUID变量的作用

    场景:你在公司开发一个类(记为LogManager,用来管理日志,拥有变量Date date,String description,我们使用序列化保存日志信息),并且已经上线使用(已经积累了一些数据),随着时间的推移,你发现你这个类设计的不够完善,因此你需要添加一个实例变量 int errorID,那么你如果按照一般反序列化方法(readObject),则会抛出:InvalidClassException。那么怎么能够成功将原始的数据转换成新版本的LogManager对象呢?

    解决:

    1. 使用 Java 提供的serialver命令: serialver  LogManager 计算出原始LogManager的 serialVersionUID(比如为123L)。
    2. 在新版本的LogManager中添加:  static final long serialVersionUID = 123L;

    目的:保持不同版本类的序列化的兼容性

    序列化类A(类A继承自类B),但是类B不可序列化,怎么办?

    默认情况:

    1. 先将类A的实例变量全部还原。
    2. 因为类A继承类B,因此类A的对象也会有类B的实例变量,对于类B的实例变量,调用类B的默认无参构造函数初始化类B的实例变量。(一定要定义超类无参构造函数,不然会抛 no valid constructor)

    解决:自定义readObject和writeObject。

    对序列化的数据加密

    问题:我们知道,序列化主要用于数据传输,但是序列化的数据是可以反序列化的,因此黑客可以直接把你的数据截下来(比如你的序列化文件为data.txt,用WinHeX打开后,基本就能看到所涉及的类和你传输的数据)。那么怎么样能够加密序列化的数据呢?
    解决:通过自定义的序列化方法(在要序列化的对象中实现readObject和writeObject方法)。

    • private void writeObject(ObjectOutputStream os)throws IOException    // 你如果重写时必须是private的。
    • private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException   //你如果重写时必须是private的
     1 import java.io.*;
     2 public class Serialize05
     3 {
     4     public static void main(String[] args) throws Exception{
     5         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
     6         out.writeObject(new Person("admin","abc123"));
     7         out.close();
     8         ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
     9         Person person = (Person)in.readObject();
    10         System.out.println(person);    
    11     }
    12 }
    13 class Person implements Serializable
    14 {
    15     String name;
    16     String password;
    17     public Person(String name,String password)
    18     {
    19         this.name = name;
    20         this.password = password;
    21     }
    22     private void writeObject(ObjectOutputStream os)throws IOException
    23     {
    24         os.writeUTF(name);
    25         byte[] pass = password.getBytes("UTF-8");
    26         for(int i=0;i<pass.length;i++)
    27         {
    28             pass[i] = (byte)(pass[i] ^ 32);        //对密码加密
    29         }
    30         os.write(pass);
    31     }
    32     private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
    33     {
    34         name = is.readUTF();
    35         byte[] pass = new byte[1024];
    36         int size = is.read(pass,0,1024);
    37         for(int i=0;i<pass.length;i++)
    38         {
    39             pass[i] = (byte)(pass[i] ^ 32);        //对密码解密
    40         }
    41         String password = new String(pass,0,size,"UTF-8");
    42         this.password = password;
    43     }
    44     public String toString()
    45     {
    46         return "name="+name+",password="+password;
    47     }
    48 }
    View Code

    利用序列化实现深层复制

     1 /*
     2     用序列化实现深层复制
     3 */
     4 import java.io.*;
     5 public class Serialize09
     6 {
     7     public static void main(String[] args) throws Exception{
     8         ByteArrayOutputStream bout = new ByteArrayOutputStream();
     9         ObjectOutputStream out = new ObjectOutputStream(bout);
    10         Person f1 = new Person("f1",null);
    11         Person p1 = new Person("u1",f1);
    12         out.writeObject(p1);
    13         byte[] b = bout.toByteArray();
    14         ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b));
    15         Person p2 = (Person)in.readObject();
    16         p2.friend.name = "f2";
    17         System.out.println(p1.friend.name);    //输出:f1.虽然p2的朋友名字改变了,但是p1的朋友没改。
    18 
    19     }
    20 }
    21 class Person implements Serializable
    22 {
    23     String name;
    24     Person friend;
    25     public Person(String name,Person friend)
    26     {
    27         this.name = name;
    28         this.friend = friend;
    29     }
    30 }
    View Code

    序列化包含别人开发过的不可序列化的类

    问题:如果你想要开发一个 House 类,此时别人已经开发好的 Furniture 类(Furniture 类中有实例变量int size,没有类Furniture的源代码,只有class文件,且类Furniture不是可序列化的)。因为House HAS-A Furniture,因此需要组合,因此在House类中需要声明一个Furniture的实例变量,但是Furniture并不能实例化,如果按照一般的方法,则会抛“NoSerializableException”。

    解决

    1. 将Furniture实例变量设为transient。
    2. 在House类中实现readObject和writeObject方法。

    实现方法

    • private void writeObject(ObjectOutputStream os)throws IOException
    • private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
     1 import java.io.*;
     2 public class Serialize03
     3 {
     4     public static void main(String[] args) throws Exception {
     5         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
     6         out.writeObject(new A(new B(10),100));
     7         out.close();
     8         ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
     9         A a = (A)in.readObject();
    10         System.out.println(a);        //输出:b=10,a=100
    11     }
    12 }
    13 class B       //别人已经开发好的类,且假设看不到B的源代码
    14 {
    15     int bb;
    16     public B(int bb)
    17     {
    18         this.bb = bb;
    19     }
    20 }
    21 class A implements Serializable
    22 {
    23     transient B b;
    24     int a;
    25     public A(B b,int a)
    26     {
    27         this.b = b;
    28         this.a = a;
    29     }
    30     public String toString()
    31     {
    32         return "b="+b.bb+",a="+a;
    33     }
    34     private void writeObject(ObjectOutputStream os)throws IOException    //手工序列化B b
    35     {
    36         os.defaultWriteObject();
    37         os.writeInt(b.bb);
    38     }
    39     private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
    40     {
    41         is.defaultReadObject();
    42         b = new B(is.readInt());
    43     }
    44 }
    View Code

    writeReplace和readResolve方法

    如果有一个类A、类AProxy,如果想要实现以下任务:当 writeObject(A a) 时,实际写入的是AProxy对象(代理类),则可以使用readResolve和writeReplace。

    • 在类A中实现writeReplace方法(当ObjectOutputStream.writeObject(A)时调用该方法)
    • Object writeReplace() throws ObjectStreamException。
    • 在类AProxy中实现 readResolve 方法(当ObjectInputStream.readObject(A)时调用该方法)
    • Object readResolve() throws ObjectStreamException。
     1 import java.io.*;
     2 public class Serialize07
     3 {
     4     public static void main(String[] args) throws Exception{
     5         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
     6         out.writeObject(new Person("admin",20));
     7         out.close();
     8         ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
     9         Person person = (Person)in.readObject();
    10         System.out.println(person);    
    11     }
    12 }
    13 
    14 class PersonProxy implements Serializable
    15 {
    16     String data;
    17     public PersonProxy(Person person)
    18     {
    19         data = person.name+","+person.age;
    20     }
    21     private Object readResolve() throws ObjectStreamException
    22     {
    23         System.out.println("调用了readResolve方法");
    24         String name = data.split(",")[0];
    25         int age = Integer.parseInt(data.split(",")[1]);
    26         Person person = new Person(name,age);
    27         return person;
    28     }
    29 }
    30 class Person implements Serializable
    31 {
    32     String name;
    33     int age;
    34     public Person(String name,int age)
    35     {
    36         this.name = name;
    37         this.age = age;
    38     }
    39     private Object writeReplace() throws ObjectStreamException
    40     {
    41         System.out.println("调用了writeReplace方法");
    42         return new PersonProxy(this);
    43     }
    44     public String toString()
    45     {
    46         return "name=" + name + ",age=" + age;
    47     }
    48 }
    View Code

    Externalizable接口

    用途:自定义流格式,比如类A没有实现序列化,而类B继承类A,而类B需要负责包括超类数据的保存和恢复。

    实现方法

    • void writeExternal(ObjectOutput out) throws IOException
    • void readExternal(ObjectInput in) throws IOException,ClassNotFoundException

    从API中可以看出,Externalizable接口实现了Serializable接口。
    如果一个类A实现了Externalizable接口,则

    • ObjectInputStream.readObject()时会调用readExternal(),而不是readObject()
    • ObjectOutputStream.writeObject()时会调用writeExternal(),而不是writeObject()

    当ObjectInputStream.readObject()时,过程如下:

    1. 假设读取类A的对象,则首先调用类A的无参构造函数(只有在实现Externalizable接口时才会调用无参构造函数),因此我们必须要实现这个构造函数。
    2. 因为类A实现了Externalizable接口,则调用readExternal()。
     1 import java.io.*;
     2 public class Serialize06
     3 {
     4     public static void main(String[] args) throws Exception{
     5         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
     6         out.writeObject(new Employee("admin",20,1000));
     7         out.close();
     8         ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
     9         Employee person = (Employee)in.readObject();
    10         System.out.println(person);    
    11     }
    12 }
    13 
    14 class Person
    15 {
    16     String name;
    17     int age;
    18     public Person()
    19     {
    20     }
    21 }
    22 class Employee extends Person implements Externalizable
    23 {
    24     transient double salary;
    25     public Person()
    26     {
    27     }
    28     public Employee(String name,int age,double salary)
    29     {
    30         this.name = name;
    31         this.age = age;
    32         this.salary = salary;
    33     }
    34     public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException 
    35     {
    36         name = in.readUTF();
    37         age = in.readInt();
    38         salary = in.readDouble();
    39     }
    40     public void writeExternal(ObjectOutput out)throws IOException
    41     {
    42         out.writeUTF(name);
    43         out.writeInt(age);
    44         out.writeDouble(salary);
    45     }
    46     public String toString()
    47     {
    48         return "name=" + name + ",age=" + age + ",salary=" + salary;
    49     }
    50 }
    View Code

    Reference

    [1]http://www.ibm.com/developerworks/cn/java/j-5things1/
    [
    2]Java核心技术(第7版) P617~637

  • 相关阅读:
    免费第三方API平台整合
    接口使用数据库缓存考虑的不周到之处
    找了两个小时的错误,net.sf.json.JSONException: JSON keys cannot be null.
    jsp动态页面访问报错:HTTP Status 500
    JAVA中json转换为集合(对象)之间的相互转换
    听头条
    使用DataOutputStream输出流的read方法出现读取字节不一致解决办法,本地和测试环境不一致
    ibatis中的xml配置文件
    poj 1325 Machine Schedule 题解
    poj 1469 COURSES 题解
  • 原文地址:https://www.cnblogs.com/xiazdong/p/3188808.html
Copyright © 2020-2023  润新知