zoukankan      html  css  js  c++  java
  • 序列化与反序列化

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

    把对象转换为字节序列的过程称为对象的序列化
    把字节序列恢复为对象的过程称为对象的反序列化
    对象的序列化主要有两种用途:

    1. 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
    2. 在网络上传送对象的字节序列。

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

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

    二、JDK类库中的序列化API

    Java中序列化与反序列化用到两个对象流:

    1. java.io.ObjectOutputStream:对象输出流,它的writeObject(Object obj)方法可对参数指定的Object对象进行序列化,把得到的字节序列写到一个目标输出流中。
    2. java.io.ObjectInputStream:对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    对于上面两个对象流,还需要两个流:

    • 保存到到本地硬盘
      1. java.io.FileOutputStream:对象序列化为字节输出到本地硬盘
      2. java.io.FileInputStream:从本地硬盘读取字节反序列化
    • 网络传输
      1. java.io.ByteArrayOutputStream:将对象序列化为字节
      2. java.io.ByteArrayInputStream:从字节反序列化为对象

    Java实现序列化与反序列化提供有两个接口:

    1. Serializable接口:实现Serializable接口的类可以 采用默认的序列化方式
    2. Externalizable接口:实现Externalizable接口的类完全由自身来控制序列化的行为

    只有实现了Serializable和Externalizable接口的类的对象才能被序列化。

    对象序列化步骤:

    1. 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
    2. 通过对象输出流的writeObject()方法写对象。

    对象反序列化的步骤如下:

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

    实现Serializable的序列化与反序列化

    对象序列化保存在本地硬盘

    public class UserInfoTest {
        /**
         * 从内存序列化到本地
         */
        @Test
        public void serialize() throws IOException {
            UserInfo userInfo = new UserInfo();
            userInfo.setName("张三");
            userInfo.setAge(23);
    
            //序列化到本地
            FileOutputStream fileOutputStream = new FileOutputStream("./aa.txt");
            ObjectOutputStream os = new ObjectOutputStream(fileOutputStream);
            os.writeObject(userInfo);
            System.out.println("序列化成功");
        }
    
        /**
         * 从本地反序列化到内存
         */
        @Test
        public void deserialize() throws IOException, ClassNotFoundException {
            FileInputStream fileInputStream = new FileInputStream("./aa.txt");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            UserInfo userInfo = (UserInfo) inputStream.readObject();
            System.out.println("反序列化成功:" + userInfo);
        }
    }
    @Data
    class UserInfo implements Serializable {
        private String name;
        private Integer age;
    }
    

    网络传输(使用RabbitMQ测试)

    /**
     * @author Hayson
     * @date 2018/11/13 09:08
     * @description RabbitMQ测试序列化对象
     */
    public class Send {
        private final static String QUEUE_NAME = "test_serializable";
        private final static String HOST = "192.168.239.134";
        private final static Integer PORT = 5672;
        private final static String USERNAME = "admin";
        private final static String PASSWORD = "admin";
    
        public static void main(String[] args) throws IOException, TimeoutException {
            Send();		//发送消息
            Recv();		//接收消息
        }
        /**
         * 对象序列化为字节数组,发送字节数组消息
         * @throws IOException
         * @throws TimeoutException
         */
        private static void Send() throws IOException, TimeoutException {
            Connection connection = getConnection();
            Channel channel = connection.createChannel();
    
            channel.exchangeDeclare(QUEUE_NAME, BuiltinExchangeType.DIRECT);
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.queueBind(QUEUE_NAME, QUEUE_NAME, QUEUE_NAME);
    
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("张三");
            userInfo.setPassWord("23");
    
            //对象序列化为字节数组
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(512);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(userInfo);
            byte[] userInfoBytes = outputStream.toByteArray();
    
            channel.basicPublish(QUEUE_NAME, QUEUE_NAME, null, userInfoBytes);
            System.out.println("发送对象字节大小 = " + userInfoBytes.length);
    
            objectOutputStream.close();
            connection.close();
        }
        /**
         * 接收字节数组消息,反序列化为对象
         * @throws IOException
         * @throws TimeoutException
         */
        private static void Recv() throws IOException, TimeoutException {
            Connection connection = getConnection();
            Channel channel = connection.createChannel();
    
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, 
                                           Envelope envelope, 
                                           AMQP.BasicProperties properties, 
                                           byte[] body) throws IOException {
                    // 把接收到的字节数组反序列化为对象
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
                    ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                    UserInfo userInfo = null;
                    try {
                        userInfo = (UserInfo) objectInputStream.readObject();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }finally{
                        objectInputStream.close();
                    }
                    System.out.println(userInfo);
                }
            };
            channel.basicConsume(QUEUE_NAME, true, consumer);
        }
        /**
         * 获取Connection连接
         * @return
         * @throws IOException
         * @throws TimeoutException
         */
        private static Connection getConnection() throws IOException, TimeoutException {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost(HOST);
            factory.setPort(PORT);
            factory.setUsername(USERNAME);
            factory.setPassword(PASSWORD);
    
            return factory.newConnection();
        }
    }
    @Data
    class UserInfo implements Serializable{
        private String name;
        private Integer age;
    }
    

    上面运行的序列化和反序列化都没有问题,但是如果我们序列化了对象到硬盘,而项目在后期再原来的UserInfo类的基础上添加成员变量private String address;

    @Data
    class UserInfo implements Serializable{
        private String name;
        private Integer age;
        private String address;//新添加成员变量
    }
    

    添加新成员变量后,再从本地反序列化,则会有以下异常(截取部分):

    java.io.InvalidClassException: com.hayson.config.UserInfo; local class incompatible: stream classdesc serialVersionUID = 6288102273809508157, local class serialVersionUID = -4025030802652115000
    

    出现异常原因:

    serialVersionUID是用于记录class文件的版本信息的,serialVersionUID这个数字是JVM(JAVA虚拟机)通过一个类的类名、成员、包名、工程名算出的一个数字。而这时候序列化文件中记录的serialVersionUID与项目中的不一致,即找不到对应的类来反序列化。

    serialVersionUID

    如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后
    在序列化与反序列化的时候,jvm都不会再自己算这个 classserialVersionUID了。

    去掉刚才添加的成员变量;,并且在User类中指定一个serialVersionUID

       @Data
        private static class UserInfo implements Serializable {
            private static final long serialVersionUID = 7078078434151067638L;
            private String name;
            private Integer age;
            //private String address;
        }
    

    重新序列化到aa.txt文件中,然后再在javaBean中添加private String address; 新成员变量(将上面的注释去掉),再执行反序列化操作,此时运行不再出现上面的异常情况,并且执行反序列化结果与javaBean中的成员变量增加不影响结果。

    如果在UserInfo类中再添加成员变量,而这个变量为一个class ,如Address,那么Address类也必须要实现Serializable接口。

    @Data
    private static class UserInfo implements Serializable {
        private static final long serialVersionUID = 7078078434151067638L;
        private String name;
        private Integer age;
        private Address address;	//新增成员变量是一个类,这个类必须实现Serializable
    }
    class Address implements Serializable{
        private String country;
        private String city;
    }
    

    transient关键字

    transient关键字,当你不想要某些字段序列化,可以用transient关键字修饰

    @Data
    private static class UserInfo implements Serializable {
        private static final long serialVersionUID = 7078078434151067638L;
        private String name;
        private Integer age;
        transient String address;//新添加成员变量,有transient关键字,不序列化该字段
    }
    
  • 相关阅读:
    GitLab用户权限管理
    类似vant中的tab实现
    Gitgitee/github/gitlab账号分离
    Vim操作
    partition by 用法
    crontab执行feat_gen.sh时,报错找不到pyspark
    SQL同一个字段出现null和0值,有何区别,原因是什么?left join导致null值出现,case when导致0值出现
    linux 定时任务crontab的用法
    卡方检验
    ROC与AUC
  • 原文地址:https://www.cnblogs.com/zenghi-home/p/10069492.html
Copyright © 2011-2022 走看看