美文网首页
序列化与反序列化

序列化与反序列化

作者: 殷俊杰 | 来源:发表于2018-08-31 21:53 被阅读0次

    Java基础学习总结——Java对象的序列化和反序列化

    一、序列化和反序列化的概念

    把对象转换为字节序列的过程称为对象的序列化
      把字节序列恢复为对象的过程称为对象的反序列化
      对象的序列化主要有两种用途:
      1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
      2) 在网络上传送对象的字节序列。

    在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

    当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

    二、JDK类库中的序列化API

    java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
      java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
      只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
      对象序列化包括如下步骤:
      1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
      2) 通过对象输出流的writeObject()方法写对象。

    对象反序列化的步骤如下:
      1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
      2) 通过对象输入流的readObject()方法读取对象。

    三、对象序列化和反序列范例

    package com.yjj.serialize.object;
    
    import java.io.Serializable;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:08
     * @Version: 1.0
     */
    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private Integer age;
        private String name;
    
        public Person() {
        }
    
        public Person(Integer age, String name) {
            this.age = age;
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    序列化

    package com.yjj.serialize.serialize;
    
    import com.yjj.serialize.object.Person;
    
    import java.io.*;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:06
     * @Version: 1.0
     */
    public class SerializeTest {
        public static void main(String[] args) {
            Person person = new Person(12, "殷俊杰");
            try {
                serialize(person);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void serialize(Object object) throws IOException {
            Class clazz = object.getClass();
            System.out.println(clazz.getName());
            File file = new File("E:/a.txt");
            FileOutputStream fileOutputStream = null;
            ObjectOutputStream objectOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(file);
                objectOutputStream = new ObjectOutputStream(fileOutputStream);
                objectOutputStream.writeObject(object);
                System.out.printf("序列化对象%s成功",clazz.getName());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            }
        }
    }
    

    输出如下

    com.yjj.serialize.object.Person
    序列化对象com.yjj.serialize.object.Person成功
    Process finished with exit code 0
    

    反序列化

    package com.yjj.serialize.deserialize;
    
    import com.yjj.serialize.object.Person;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:45
     * @Version: 1.0
     */
    public class DeserializeTest {
        public static void main(String[] args) {
            File file=new File("E:/a.txt");
            Object object=deserialize(file);
            Person person= (Person) object;
            System.out.println(person);
        }
    
        public static Object deserialize(File file){
            try {
                ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
                Object object=objectInputStream.readObject();
                return object;
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    
    

    输出如下

    Person{age=12, name='殷俊杰'}
    

    serialVersionUID的作用

    在序列化时显示指定serialVersionUID作为版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量,如果不显示指定,jvm会隐式给我们随机生成一个serialVersionUID,这个serialVersionUID是根据对象的信息生成的,如果我们对序列化对象有所改动的话,serialVersionUID也会随之改动,再反序列化就会因serialVersionUID不一致而报错、
    示例:

    package com.yjj.serialize.object;
    
    import java.io.Serializable;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:36
     * @Version: 1.0
     */
    public class User implements Serializable{
        private String name;
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + 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;
        }
    }
    
    

    序列化和反序列化跟上面一样

    package com.yjj.serialize.serialize;
    
    import com.yjj.serialize.object.User;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:06
     * @Version: 1.0
     */
    public class SerializeTest2 {
        public static void main(String[] args) {
            User user = new User("殷俊杰",16);
            try {
                serialize(user);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void serialize(Object object) throws IOException {
            Class clazz = object.getClass();
            System.out.println(clazz.getName());
            File file = new File("E:/b.txt");
            FileOutputStream fileOutputStream = null;
            ObjectOutputStream objectOutputStream = null;
            try {
                fileOutputStream = new FileOutputStream(file);
                objectOutputStream = new ObjectOutputStream(fileOutputStream);
                objectOutputStream.writeObject(object);
                System.out.printf("序列化对象%s成功",clazz.getName());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            }
        }
    }
    

    反序列化

    package com.yjj.serialize.deserialize;
    
    import com.yjj.serialize.object.User;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:45
     * @Version: 1.0
     */
    public class DeserializeTest2 {
        public static void main(String[] args) {
            File file=new File("E:/b.txt");
            Object object=deserialize(file);
            User user= (User) object;
            System.out.println(user);
        }
    
        public static Object deserialize(File file){
            try {
                ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
                Object object=objectInputStream.readObject();
                return object;
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    
    

    此时是正常且成功的,但是,我们如果现在在User对象新增一个属性

    package com.yjj.serialize.object;
    
    import java.io.Serializable;
    
    /**
     * @Description:
     * @Author: yinjunjie
     * @CreateDate: 2018/8/31 21:36
     * @Version: 1.0
     */
    public class User implements Serializable{
        private String name;
        private int age;
        private String gender;
    
        public User(String name, int age, String gender) {
            this.name = name;
            this.age = age;
            this.gender = gender;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", gender='" + gender + '\'' +
                    '}';
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        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;
        }
    }
    

    上面我说,serialVersionUID是根据对象信息生成的,现在对象多了一个属性,信息已经改变,再次生成的serialVersionUID 已经和刚才的不一样了,这时我们再进行反序列化会报如下错误

    D:\software\jdk\bin\java -javaagent:D:\software\idea\lib\idea_rt.jar=10101:D:\software\idea\bin -Dfile.encoding=UTF-8 -classpath D:\software\jdk\jre\lib\charsets.jar;D:\software\jdk\jre\lib\deploy.jar;D:\software\jdk\jre\lib\ext\access-bridge-64.jar;D:\software\jdk\jre\lib\ext\cldrdata.jar;D:\software\jdk\jre\lib\ext\dnsns.jar;D:\software\jdk\jre\lib\ext\jaccess.jar;D:\software\jdk\jre\lib\ext\jfxrt.jar;D:\software\jdk\jre\lib\ext\localedata.jar;D:\software\jdk\jre\lib\ext\nashorn.jar;D:\software\jdk\jre\lib\ext\sunec.jar;D:\software\jdk\jre\lib\ext\sunjce_provider.jar;D:\software\jdk\jre\lib\ext\sunmscapi.jar;D:\software\jdk\jre\lib\ext\sunpkcs11.jar;D:\software\jdk\jre\lib\ext\zipfs.jar;D:\software\jdk\jre\lib\javaws.jar;D:\software\jdk\jre\lib\jce.jar;D:\software\jdk\jre\lib\jfr.jar;D:\software\jdk\jre\lib\jfxswt.jar;D:\software\jdk\jre\lib\jsse.jar;D:\software\jdk\jre\lib\management-agent.jar;D:\software\jdk\jre\lib\plugin.jar;D:\software\jdk\jre\lib\resources.jar;D:\software\jdk\jre\lib\rt.jar;D:\workspace\demo-all\serialize\target\classes com.yjj.serialize.deserialize.DeserializeTest2
    null
    java.io.InvalidClassException: com.yjj.serialize.object.User; local class incompatible: stream classdesc serialVersionUID = 6219457185932359615, local class serialVersionUID = 7483460167538010623
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
        at com.yjj.serialize.deserialize.DeserializeTest2.deserialize(DeserializeTest2.java:27)
        at com.yjj.serialize.deserialize.DeserializeTest2.main(DeserializeTest2.java:19)
    
    Process finished with exit code 0
    
    

    我们指定serialVersionUID后就可以随意修改了

    网络编程发送对象示例

    总结

    1. 如果对象需要被写出到文件上,那么对象所属的类必须要实现Serializable接口。 Serializable接口没有任何的方法,是一个标识接口而已。
    2. 对象的反序列化创建对象的时候并不会调用到构造方法的、(这点文中没有说到,想要验证的同学在构造方法后面加一句System.out.println("构造方法执行吗?");,实际上构造方法是不执行的,自然这句话也没有输出了)
    3. serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
    4. 使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID
      进行对比,如果这两个id不一致,反序列则失败。
    5. 如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
      在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了。
    6. 如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰。
    7. 如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口。

    相关文章

      网友评论

          本文标题:序列化与反序列化

          本文链接:https://www.haomeiwen.com/subject/nnwavftx.html