分布式通信——初探序列化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yp1125/article/details/81172940

初探序列化
我们还是按部就班地进行wwh:

  • 什么是序列化(what)
  • 为什么使用序列化(why)
  • 如何使用序列化(how)
    • 序列化的演变
    • 如何实现一个序列化操作
    • serializableUID的作用
    • 静态变量序列化
    • transient关键字
    • 父子类序列化
    • 序列化的存储规则
    • 序列化实现深度克隆

什么是序列化

序列化是将对象的状态信息进行转换可以存储或传递的形式的过程。序列化使其他代码可以查看或修改,那些不序列化便无法访问的对象实例数据。

为什么使用序列化

1.以某种存储形式使自定义对象持久化;
2.将对象由一个地方传递到另一个地方;
3.使程序更具维护性,合适的序列化协议不仅可以提高系统的通用性,强壮性,安全性,优化性能,同时还能让系统更加易于调式和扩展。

如何使用序列化

1.序列化的演变

1.1 Java的Serializable接口

Java序列化机制Serializable接口,该序列化机制本身存在的问题。

1.序列化数据结果比较大,传输效率比较低
2.不能跨语言传输

1.2 WebService基于SOAP协议(XML)

由于Java序列化的不健全,XML编码格式的对象序列成为了主流,解决以下问题。

1.可多语言兼容,可跨语言
2.XML编码容易理解

1.3 HTTP RESTful基于HTTP协议(JSON)

http + json解决了以下问题。

1.基于HTTP协议无状态
2.JSON的可读性比XML更容易理解,解析规则也简单

1.4 移动互联网场景下通信协议

ProtocolBuffers、MessagePack等,解决了以下问题。

1.JSON是纯文本协议,优点是可读性高,使用简单方便;而正是它的优点造成了它解析费时、解析内存耗费高。
2.JSON序列化结果数据量大的问题。

2.如何实现一个序列化操作

Java类实现Serializabel接口

实体类Person

package com.feiniu;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1129345809863719029L;

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

序列化测试类SerializableTest

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
        //反序列化
        DeSerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            System.out.println("序列化成功!!!");
            oos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializablePerson() {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person = (Person) ois.readObject();
            System.out.println(person);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

控制台输出结果

序列化成功!!!
Person [name=yupan, age=30]

案例总结

ObjectOutputStream:表示读取对象转换为指定的字节数据
ObjectInputStream:表示读取指定的字节数据转换为对象

3.serialVersionUID的作用

接下来执行代码验证一下
实体类Person

package com.feiniu;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1129345809863719029L;

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

序列化操作
序列化测试类SerializableTest只执行序列化方法SerializablePerson(),注释了反序列化方法DeSerializablePerson()。

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
        //反序列化
        //DeSerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            System.out.println("序列化成功!!!");
            oos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializablePerson() {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person = (Person) ois.readObject();
            System.out.println(person);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

注释serialVersionUID
然后注释Person类的serialVersionUID = -1129345809863719029L,修改后的Person类

package com.feiniu;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    //private static final long serialVersionUID = -1129345809863719029L;

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

反序列化
序列化测试类SerializableTest只执行反序列化方法DeSerializablePerson(),注释了序列化方法SerializablePerson()。

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        //SerializablePerson();
        //反序列化
        DeSerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            System.out.println("序列化成功!!!");
            oos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializablePerson() {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person = (Person) ois.readObject();
            System.out.println(person);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

控制台输出结果

java.io.InvalidClassException: com.feiniu.Person; local class incompatible: stream classdesc serialVersionUID = -1129345809863719029, local class serialVersionUID = -7539772182737047037
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    at com.feiniu.SerializableTest.DeSerializablePerson(SerializableTest.java:43)
    at com.feiniu.SerializableTest.main(SerializableTest.java:17)

案例总结

1.我们可以看见控制台信息报错了,回顾一下之前的操作过程:序列化Person类—>注释serializableUID—>反序列化Person类
2.由控制台信息可以看出stream classdesc serialVersionUID = -1129345809863719029, local class serialVersionUID = -7539772182737047037,序列化类编译到JVM中的是serialVersionUID = -1129345809863719029,反序列化操作发现Person类没有设置serialVersionUID ,编译器自动进行摘要算法生成serialVersionUID = -7539772182737047037。
3.因为在Java中必须保证序列化和反序列化的serialVersionUID相同,所以控制台报错了,验证了serializableUID的作用特性,能保证序列化对象和反序列化对象一致。

4.静态变量序列化

实体类Person增加静态变量height

package com.feiniu;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1129345809863719029L;

    private String name;

    private int age;

    public static int height = 2;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

序列化测试类SerializableTest,新增赋值age以及height属性

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
        //反序列化
        DeSerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            System.out.println("序列化成功!!!");
            oos.close();

            //验证静态变量
            person.setAge(28);
            Person.height = 5;

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializablePerson() {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person = (Person) ois.readObject();
            System.out.println(person);
            //验证静态变量
            System.out.println("height==" + person.height);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

控制台输出结果

序列化成功!!!
Person [name=yupan, age=30]
height==5

案例总结

1.序列化方法SerializablePerson()内执行writeObject后,给age赋值28,给height赋值5;
2.控制台输出结果age还是序列化的值为30,但是height是序列化之后的值为5;
3.age是普通变量,而height为static静态变量,说明了序列化并不保存静态变量的状态。

5.transient关键字

实体类Person,给name设置transient关键字

package com.feiniu;

import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1129345809863719029L;

    private transient String name;

    private int age;

    public static int height = 2;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

序列化测试类SerializableTest正常执行

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
        //反序列化
        DeSerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            System.out.println("序列化成功!!!");
            oos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializablePerson() {
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person = (Person) ois.readObject();
            System.out.println(person);
            //验证静态变量
            //System.out.println("height==" + person.height);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

控制台输出结果

序列化成功!!!
Person [name=null, age=30]

案例总结

从控制台输出结果来看,name为null值,说明transient起了作用,验证了transient指定属性不参与序列化。

6.父子类序列化

实体父类SuperUser

package com.feiniu.parent;

public class SuperUser {

    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

实体子类User

package com.feiniu.parent;

import java.io.Serializable;

public class User extends SuperUser implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 7323479849062274277L;

    @Override
    public String toString() {
        return "User [getAge()=" + getAge() + "]";
    }

}

父子类的测试类

package com.feiniu.parent;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SuperDemo {
    public static void main(String[] args) {
        //序列化
        serializableUser();
        //反序列化
        DeSerializableUser();
    }

    private static void serializableUser() {
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("user")));
            User user = new User();
            user.setAge(18);
            oos.writeObject(user);
            System.out.println("序列化成功!!!");
            oos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void DeSerializableUser() {
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("user")));
            User user = (User) ois.readObject();
            System.out.println(user);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


}

控制台输出结果

序列化成功!!!
User [getAge()=0]

案例总结

父类未实现序列化,子类实现了序列化,在序列化方法里设置了setAge(18),最终getAge()=0,验证了如果父类没有实现序列化,而子类实现了序列化。那么父类中的成员没有办法做序列化操作。

7.序列化的存储规则

对person同一对象两次写入文件

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            //person写入两次
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            oos.flush();
            System.out.println("第一次序列化成功!字节大小为:" + new File("person").length());
            oos.writeObject(person);
            oos.flush();
            System.out.println("第二次序列化成功!字节大小为:" + new File("person").length());
            oos.close();

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person1 = (Person) ois.readObject();
            Person person2 = (Person) ois.readObject();
            System.out.println(person1 == person2);
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

控制台输出结果

第一次序列化成功!字节大小为:84
第二次序列化成功!字节大小为:89
true

案例总结

控制台输出结果分别打印出了对同一个对象写入两次的后的字节大小,然后从文件中反序列化出两个对象,比较这两个对象是否为同一个对象。正常逻辑,两次写入对象,文件大小正常来说应该会翻倍,也就是说,第二次写入后的文件大小应该是第一次写入文件大小的两倍,那么当反序列化读取文件,生成了两个对象,判断两个对象是false,但是但是但是控制台输出为true。

解答疑问

疑问:我们可以看见控制台输出的文件大小,第二次写入对象文件只是增加了5个字节,并且两个对象是相等的,why???
解答:Java序列化机制为了节省磁盘空间,具有特定的存储规则(具体规则暂时不去展开了),当写入文件的对象为同一对象时,并不会再将对象的内容进行存储,而是再次存储一份对象的引用,增加的5个字节就是新增的对象引用和一些控制信息的空间。反序列化时,恢复引用关系,使得person1和person2指向同一对象,二者相等,所以输出结果为true。

特殊案例测试

package com.feiniu;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    public static void main(String[] args) {
        //序列化
        SerializablePerson();
    }

    private static void SerializablePerson() {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(new FileOutputStream(new File("person")));
            //person写入两次
            Person person = new Person();
            person.setName("yupan");
            person.setAge(30);
            oos.writeObject(person);
            oos.flush();
            System.out.println("第一次序列化成功!字节大小为:" + new File("person").length());
            person.setName("zhangsha");
            oos.writeObject(person);
            oos.flush();
            System.out.println("第二次序列化成功!字节大小为:" + new File("person").length());
            oos.close();

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("person")));
            Person person1 = (Person) ois.readObject();
            Person person2 = (Person) ois.readObject();
            System.out.println("第一次name:" + person1.getName());
            System.out.println("第二次name:" + person2.getName());
            ois.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

控制台输出结果

第一次序列化成功!字节大小为:84
第二次序列化成功!字节大小为:89
第一次name:yupan
第二次name:yupan

案例总结

控制台输出结果想明白了吗?

8.序列化实现深度克隆

Person实体类,实现了深度克隆方法

package com.feiniu;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = -1129345809863719029L;

    private String name;

    private int age;

    public static int height = 2;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 序列化深度克隆
     * @return
     */
    public Object deepClone () {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

}

PersonTest测试类

package com.feiniu;

public class PersonTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("yupan");
        person.setAge(30);

        Person person2 = (Person) person.deepClone();
        person2.setName("zhangsha");
        person2.setAge(26);

        System.out.println(person);
        System.out.println(person2);
    }
}

控制台输出结果

Person [name=yupan, age=30]
Person [name=zhangsha, age=26]

案例总结

从控制台输出结果来看是两个对象,被克隆的对象和克隆对象应该不指向同一个引用,输出信息也验证了这个特点。
浅克隆:复制对象,两个对象指向同一个引用
深克隆:复制对象,除去了引用信息,两个对象不指向同一个引用

阅读更多
换一批

没有更多推荐了,返回首页