关于序列化:serialVersionUID作用是什么以及如何生成的

1次阅读

共计 7658 个字符,预计需要花费 20 分钟才能阅读完成。

失常不设置 serialVersionUID 的序列化和反序列化

先定义一个实体 Student.class, 须要实现 Serializable 接口,然而不须要实现 get(),set()办法


import java.io.Serializable;
public class Student implements Serializable {
    private int age;
    private String name;
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

测试类,思路是先把 Student 对象序列化到 Student.txt 文件,而后再讲 Student.txt 文件反序列化成对象,输入。

public class SerialTest {public static void main(String[] args) {serial();
        deserial();}
    // 序列化
    private static void serial(){Student student = new Student(9, "Mike");
        try {FileOutputStream fileOutputStream = new FileOutputStream("Student.txt");
            ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(student);
            objectOutputStream.flush();} catch (Exception exception) {exception.printStackTrace();
        }
    }
    // 反序列化
    private static void deserial() {
        try {FileInputStream fis = new FileInputStream("Student.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Student student = (Student) ois.readObject();
            ois.close();
            System.out.println(student.toString());
        } catch (IOException | ClassNotFoundException e) {e.printStackTrace();
        }
    }
}

输入后果,序列化文件咱们能够看到Student.txt, 反序列化进去,外面的字段都是不变的,阐明反序列化胜利了。

序列化之后,类文件减少了字段,反序列化会怎么样?

先说后果,会失败!!!

咱们在 Student.java 中减少了一个属性 score, 从新生成了toString() 办法。

package com.aphysia.normal;

import java.io.Serializable;

public class Student implements Serializable {
    private int age;
    private String name;
    private int score;
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

而后从新调用 deserial() 办法,会报错:

java.io.InvalidClassException: com.aphysia.normal.Student; local class incompatible: stream classdesc serialVersionUID = 7488921480006384819, local class serialVersionUID = 6126416635811747983
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1963)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1829)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2120)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:482)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:440)
    at com.aphysia.normal.SerialTest.deserial(SerialTest.java:26)
    at com.aphysia.normal.SerialTest.main(SerialTest.java:7)

从下面的报错信息中,咱们能够看到,类型不匹配,次要是因为 serialVersionUID 变动了!!!

????‍♂️????‍♂️ 发问环节:我都没有设置serialVersionUID, 怎么变动的???小小的脑袋很多问号????????

正是因为没有设置,所以变动了,因为咱们减少了一个字段 score, 如果咱们不设置serialVersionUID,零碎就会主动生成,主动生成有危险,就是咱们的字段类型或者长度扭转(新增或者删除的时候),主动生成的serialVersionUID 会发生变化,那么以前序列化进去的对象,反序列化的时候就会失败。

实测:序列化实现之后,如果原类型字段缩小,不指定 serialVersionUID 的状况下,也是会报不统一的谬误。

《阿里巴巴 Java 开发手册》中规定,在兼容性降级中,在批改类的时候,不要批改 serialVersionUID 的起因。除非是齐全不兼容的两个版本。所以,serialVersionUID 其实是验证版本一致性的。

指定serialVersionUID, 缩小或者减少字段会产生什么?

咱们生成一个serialVersionUID, 办法:https://blog.csdn.net/Aphysia…。

    private static final long serialVersionUID = 7488921480006384819L;

而后执行序列化,序列化出文件 Student.txt 后,减少一个字段 score,执行反序列化。
是能够胜利的!!!只是新增的字段是默认值 0。

所以今后思考到迭代的问题的时候,个别可能减少字段或者缩小字段,都是须要思考兼容问题的,所以最好是本人指定serialVersionUID,而不是由零碎主动生成。主动生成的,因为类文件变动,它也会发生变化,就会呈现不统一的问题,导致反序列化失败。

实测:如果我缩小了字段,只有指定了serialVersionUID,也不会报错!!!

serialVersionUID 生成以及作用?

serialVersionUID是为了兼容不同版本的,在 JDK 中,能够利用 JDK 的 bin 目录下的 serialver.exe 工具产生这个serialVersionUID,对于Student.class,执行命令:serialver Student

IDEA 生成实际上也是调用这个命令,代码调用能够这样写:

ObjectStreamClass c = ObjectStreamClass.lookup(Student.class);
long serialID = c.getSerialVersionUID();
System.out.println(serialID);

如果不显示的指定,那么不同 JVM 之间的移植可能也会出错,因为不同的编译器,计算这个值的策略可能不同,计算类没有批改,也会呈现不统一的问题。
getSerialVersionUID()源码如下:

    public long getSerialVersionUID() {
        // REMIND: synchronize instead of relying on volatile?
        if (suid == null) {
            suid = AccessController.doPrivileged(new PrivilegedAction<Long>() {public Long run() {return computeDefaultSUID(cl);
                    }
                }
            );
        }
        return suid.longValue();}

能够看到下面是应用了一个外部类的形式,应用特权计算computeDefaultSUID():

    private static long computeDefaultSUID(Class<?> cl) {
        // 代理
        if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
        {return 0L;}

        try {ByteArrayOutputStream bout = new ByteArrayOutputStream();
            DataOutputStream dout = new DataOutputStream(bout);
            // 类名
            dout.writeUTF(cl.getName());

            // 修饰符
            int classMods = cl.getModifiers() &
                (Modifier.PUBLIC | Modifier.FINAL |
                 Modifier.INTERFACE | Modifier.ABSTRACT);

            //  办法
            Method[] methods = cl.getDeclaredMethods();
            if ((classMods & Modifier.INTERFACE) != 0) {classMods = (methods.length > 0) ?
                    (classMods | Modifier.ABSTRACT) :
                    (classMods & ~Modifier.ABSTRACT);
            }
            dout.writeInt(classMods);

            if (!cl.isArray()) {
                // 继承的接口
                Class<?>[] interfaces = cl.getInterfaces();
                String[] ifaceNames = new String[interfaces.length];
                for (int i = 0; i < interfaces.length; i++) {ifaceNames[i] = interfaces[i].getName();}
                // 接口名
                Arrays.sort(ifaceNames);
                for (int i = 0; i < ifaceNames.length; i++) {dout.writeUTF(ifaceNames[i]);
                }
            }
            // 属性
            Field[] fields = cl.getDeclaredFields();
            MemberSignature[] fieldSigs = new MemberSignature[fields.length];
            for (int i = 0; i < fields.length; i++) {fieldSigs[i] = new MemberSignature(fields[i]);
            }
            Arrays.sort(fieldSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {return ms1.name.compareTo(ms2.name);
                }
            });
            for (int i = 0; i < fieldSigs.length; i++) {
                // 成员签名
                MemberSignature sig = fieldSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
                     Modifier.TRANSIENT);
                if (((mods & Modifier.PRIVATE) == 0) ||
                    ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0))
                {dout.writeUTF(sig.name);
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature);
                }
            }
            // 是否有动态初始化
            if (hasStaticInitializer(cl)) {dout.writeUTF("<clinit>");
                dout.writeInt(Modifier.STATIC);
                dout.writeUTF("()V");
            }
            // 结构器
            Constructor<?>[] cons = cl.getDeclaredConstructors();
            MemberSignature[] consSigs = new MemberSignature[cons.length];
            for (int i = 0; i < cons.length; i++) {consSigs[i] = new MemberSignature(cons[i]);
            }
            Arrays.sort(consSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {return ms1.signature.compareTo(ms2.signature);
                }
            });
            for (int i = 0; i < consSigs.length; i++) {MemberSignature sig = consSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL |
                     Modifier.SYNCHRONIZED | Modifier.NATIVE |
                     Modifier.ABSTRACT | Modifier.STRICT);
                if ((mods & Modifier.PRIVATE) == 0) {dout.writeUTF("<init>");
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature.replace('/', '.'));
                }
            }

            MemberSignature[] methSigs = new MemberSignature[methods.length];
            for (int i = 0; i < methods.length; i++) {methSigs[i] = new MemberSignature(methods[i]);
            }
            Arrays.sort(methSigs, new Comparator<MemberSignature>() {public int compare(MemberSignature ms1, MemberSignature ms2) {int comp = ms1.name.compareTo(ms2.name);
                    if (comp == 0) {comp = ms1.signature.compareTo(ms2.signature);
                    }
                    return comp;
                }
            });
            for (int i = 0; i < methSigs.length; i++) {MemberSignature sig = methSigs[i];
                int mods = sig.member.getModifiers() &
                    (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
                     Modifier.STATIC | Modifier.FINAL |
                     Modifier.SYNCHRONIZED | Modifier.NATIVE |
                     Modifier.ABSTRACT | Modifier.STRICT);
                if ((mods & Modifier.PRIVATE) == 0) {dout.writeUTF(sig.name);
                    dout.writeInt(mods);
                    dout.writeUTF(sig.signature.replace('/', '.'));
                }
            }

            dout.flush();

            MessageDigest md = MessageDigest.getInstance("SHA");
            byte[] hashBytes = md.digest(bout.toByteArray());
            long hash = 0;
            for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {hash = (hash << 8) | (hashBytes[i] & 0xFF);
            }
            return hash;
        } catch (IOException ex) {throw new InternalError(ex);
        } catch (NoSuchAlgorithmException ex) {throw new SecurityException(ex.getMessage());
        }
    }

从下面这段源码大抵来看,其实这个计算 `serialVersionUID, 根本是将类名,属性名,属性修饰符,继承的接口,属性类型,名称,办法,动态代码块等等 … 这些都思考进去了,都写到一个DataOutputStream 中,而后再做hash 运算,所以说, 这个货色得指定啊,不指定的话,略微一改类的货色,就变了 …

而且这个货色指定了,没啥事,不要改!!!除非你确定两个版本就不兼容!!!
没事 …
没事..
没事.
没事

此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~

技术之路不在一时,山高水长,纵使迟缓,驰而不息。

公众号:秦怀杂货店

正文完
 0