• Java基础


    参考:https://blog.csdn.net/u011607686/article/details/78933856
    https://www.ibm.com/developerworks/cn/java/j-5things1/
    https://baijiahao.baidu.com/s?id=1633305649182361563&wfr=spider&for=pc

    Serializable序列化简介

    什么是序列化

    序列化:将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。

    反序列化:把字节序列恢复为对象的过程。

    实际上,序列化的思想是 “冻结” 对象状态,传输对象状态(写到磁盘、通过网络传输等等),然后 “解冻” 状态,重新获得可用的 Java 对象。所有这些事情的发生有点像是魔术,这要归功于 ObjectInputStream/ObjectOutputStream 类、完全保真的元数据以及程序员愿意用 Serializable 标识接口标记他们的类,从而 “参与” 这个过程。

    一个简单的序列化/反序列化过程:

    @Data
    public class SerialVO implements Serializable {
        private String str1;
        private String str2;
    }
    public class SerializableTest {
    
        /**
         * 序列化
         */
        private static void serialize() throws Exception {
            SerialVO serialVO = new SerialVO();
            serialVO.setStr1("string 1");
            serialVO.setStr2("string 2");
            //ObjectOutputStream对象输出流,将serialVO对象存储到文件中,完成对serialVO对象的序列化操作
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("/Users/iyoukeji/Desktop/test.txt")));
            oos.writeObject(serialVO);
            System.out.println("对象序列化成功!");
            oos.close();
        }
    
        /**
         * 反序列化
         */
        private static void deserialize() throws Exception {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("/Users/iyoukeji/Desktop/test.txt")));
            SerialVO serialVO = (SerialVO) ois.readObject();
            System.out.println("对象反序列化成功!");
            System.out.println(serialVO.toString());
        }
    
        public static void main(String[] args) throws Exception {
            serialize();
            deserialize();
        }
    }

    transient和static

    transient:意为“临时的”。被transient修饰的变量在序列化的时候不会被保存到文件中,通过反序列化读取这个变量时不会有值。

    static:被static修饰的变量也是不会被序列化的,因为只有堆内存会被序列化。所以静态变量会天生不会被序列化。

    修改SerialVO如下:

    @Data
    public class SerialVO implements Serializable {
        private String str1;
        private String str2;
        private static int num1 = 123;
        private transient String transientStr = "transient";
    }

    执行序列化/反序列化方法之后打印:

    对象序列化成功!
    SerialVO(str1=string 1, str2=string 2, transientStr=transient)
    对象反序列化成功!
    SerialVO(str1=string 1, str2=string 2, transientStr=null)

    serialVersionUID

    serialVersionUID字段表示类的序列化版本,用于反序列化时校验。

    在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一致则反序列化成功。否则就说明当前类跟序列化后的类发生了变化,在反序列化时就会发生crash,并且会报出InvalidClassException。

    如果类中没有这个字段,那么在运行时JVM会通过类名/方法名/属性等诸多因素计算一个值。

    推荐用户给每个需要序列化的类明确指定一个serialVersionUID。因为默认的计算方式严重依赖于编译器的实现,可能导致反序列化的时候抛出InvalidClassException异常。

    案例重现:

    (1)先执行 serialize() 方法,再将SerialVO修改为如下:

    @Data
    public class SerialVO implements Serializable {
        private String str1;
        private String str2;
        private String str3;
    }

    最后执行 deserialize() 方法,发现程序报InvalidClassException异常。

    (2)先将SerialVO修改为如下:

    @Data
    public class SerialVO implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private String str1;
        private String str2;
    }

    再执行 serialize() 方法。然后将SerialVO修改为如下:

    @Data
    public class SerialVO implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private String str1;
        private String str2;
        private String str3;
    }

    最后执行 deserialize() 方法,反序列化成功。

    序列化并不安全

    如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而反序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()。

    让 Java 开发人员诧异并感到不快的是,序列化二进制格式完全编写在文档中,并且完全可逆。实际上,只需将二进制序列化流的内容转储到控制台,就足以看清类是什么样子,以及它包含什么内容。

    这对于安全性有着不良影响。例如,当通过 RMI 进行远程方法调用时,通过连接发送的对象中的任何 private 字段几乎都是以明文的方式出现在套接字流中,这显然容易招致哪怕最简单的安全问题。

    幸运的是,序列化允许“hook”序列化过程,并在序列化之前和反序列化之后保护(或模糊化)字段数据。可以通过在 Serializable 对象上提供一个 writeObject 方法来做到这一点。

    修改SerialVO如下:

    @Data
    public class SerialVO implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private int num1;
        private String str1;
        private String str2;
    
        private void writeObject(java.io.ObjectOutputStream stream)
                throws java.io.IOException {
            // "Encrypt"/obscure the sensitive data
            num1 = num1 << 2;
            stream.defaultWriteObject();
        }
    
        private void readObject(java.io.ObjectInputStream stream)
                throws java.io.IOException, ClassNotFoundException {
            stream.defaultReadObject();
    
            // "Decrypt"/de-obscure the sensitive data
            num1 = num1 >> 2;
        }
    }

    为了“hook”序列化过程,我们在SerialVO上实现一个writeObject方法;为了“hook”反序列化过程,我们在同一个类上实现一个readObject方法。

  • 相关阅读:
    WPF之长短
    MFC程序和Win32程序的关系
    .NET Framework/CLR之长短
    常用软件
    经典推荐.Net面试法宝
    socket编程原理
    常用开发工具
    Get和Post方法的区别
    MAC IP等相关
    Datagrid为什么不自动换行显
  • 原文地址:https://www.cnblogs.com/helios-fz/p/11197221.html
Copyright © 2020-2023  润新知