serialVersionUID做用是什麼以及如何生成的?

正常不設置serialVersionUID 的序列化和反序列化

先定義一個實體Student.class,須要實現Serializable接口,可是不須要實現get(),set()方法java

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文件反序列化成對象,輸出。ide

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()方法,會報錯:ui

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變化了!!!this

🙋‍♂️🙋‍♂️ 提問環節:我都沒有設置serialVersionUID,怎麼變化的???小小的腦殼不少問號🤔🤔spa

正是由於沒有設置,因此變化了,由於咱們增長了一個字段score,若是咱們不設置serialVersionUID,系統就會自動生成,自動生成有風險,就是咱們的字段類型或者長度改變(新增或者刪除的時候),自動生成的serialVersionUID會發生變化,那麼之前序列化出來的對象,反序列化的時候就會失敗。.net

實測:序列化完成以後,若是原類型字段減小,不指定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運算,因此說,這個東西得指定啊,不指定的話,稍微一改類的東西,就變了...

並且這個東西指定了,沒啥事,不要改!!!除非你肯定兩個版本就不兼容!!!
沒事...
沒事..
沒事.
沒事

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

技術之路不在一時,山高水長,縱使緩慢,馳而不息。

公衆號:秦懷雜貨店

相關文章
相關標籤/搜索