認(rèn)識(shí)Serializable和重寫(xiě)讀寫(xiě)方法

簡(jiǎn)述

需要將對(duì)象持久化到我們的存儲(chǔ)設(shè)備上或者通過(guò)網(wǎng)絡(luò)傳輸?shù)狡渌蛻?hù)端,那么我們就需要序列化。

Serializable為Java中帶的一種序列化方式,使用起來(lái)非常簡(jiǎn)單。只需要讓類(lèi)實(shí)現(xiàn)一個(gè)Serializable接口就可以進(jìn)行序列化了。

使用

先講初步使用,后面會(huì)講到serialVersionUID。

  1. 定義一個(gè)User類(lèi),實(shí)現(xiàn)Serializable接口
public class User implements Serializable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  1. 操作User類(lèi),持久化到cache.txt文件中,并從文件中讀出來(lái)
//創(chuàng)建到User類(lèi)
        User user = new User("大灰狼", 18);
        try {
            File file = new File("cache.txt");
            if (!file.exists()) {
                file.createNewFile();
            }

            //將User類(lèi)序列化到文件中
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
            objectOutputStream.writeObject(user);

            System.out.println("序列化成功");

            //從文件中讀出來(lái)
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            User user1 = (User) objectInputStream.readObject();
            System.out.println("" + user1);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

就這樣,最簡(jiǎn)單到使用就完成了。

serialVersionUID

如果,咱們某一天改了User類(lèi)的內(nèi)部結(jié)構(gòu),如增加了一個(gè)字段:boolean isMan,但是文件類(lèi)已經(jīng)持久化的數(shù)據(jù)并沒(méi)有改動(dòng),我們需要反序列化讀出來(lái)

  • 讀取代碼
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            User user1 = (User) objectInputStream.readObject();
            System.out.println("" + user1);

我們會(huì)很明顯的看到報(bào)出一個(gè)錯(cuò)誤:

java.io.InvalidClassException: serial.User; local class incompatible: stream classdesc serialVersionUID = -3951066851478225822, local class serialVersionUID = -8381808064053356693
  • 原因:因?yàn)樵蹅兇鎯?chǔ)的時(shí)候,有幫我們自動(dòng)生成一個(gè)serialVersionUID就是-3951066851478225822,可以當(dāng)作存儲(chǔ)在文件中的類(lèi)的版本標(biāo)示。而我們本地類(lèi)版本升級(jí)了,自動(dòng)幫我們修改了版本標(biāo)示。在反序列化的時(shí)候會(huì)檢測(cè)這個(gè)版本標(biāo)示是否相同,所以在反序列化的時(shí)候就通不過(guò)了,這時(shí)候就不能反序列化了。

我們自然不想看到這種情況出現(xiàn),所以通常情況下,我們應(yīng)該手動(dòng)指定serialVersionUID的值,如123L

只需要在User中添加:

private static final long serialVersionUID = 123L;

那我們重新試試(前面的文件不能用了,刪除后再來(lái)).

  1. 先寫(xiě)文件,只包含name和age。

  2. 然后我們?cè)偕?jí)User類(lèi),在其中添了兩個(gè)字段如下:

private boolean isMan;
private String address;
  1. 再讀一次文件,打印如下
User{name='大灰狼', age=18, isMan=false, address='null'}

可以看到能成功讀取了,只是缺失字段使用了默認(rèn)值代替,做到了最大限度的恢復(fù)數(shù)據(jù)。

注意:類(lèi)升級(jí)指的是內(nèi)部結(jié)構(gòu),如果改了類(lèi)名,那是怎么都反序列化不回來(lái)的,因?yàn)轭?lèi)結(jié)構(gòu)有了毀滅性的改變,那錯(cuò)誤將是這樣的:
java.lang.ClassNotFoundException: serial.User

重寫(xiě)序列化和反序列化

就如同我們調(diào)用一樣,序列化過(guò)程其實(shí)就是通過(guò)ObjectOutputStream的writeObject、readObject來(lái)體現(xiàn)的。我們跟蹤下流程,可以看到調(diào)用到了要序列化對(duì)象的私有方法writeObject,部分代碼如下:

  1. 從ObjectOutputStream進(jìn)行寫(xiě)方法
public final void writeObject(Object obj) throws IOException {
    try {
           //調(diào)用該方法執(zhí)行寫(xiě)
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
  1. writeObject0:
private void writeObject0(Object obj, boolean unshared){
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                //查找類(lèi)信息,這里明顯使用到了反射來(lái)查找
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
}
  1. ObjectStreamClass.lookup
static ObjectStreamClass lookup(Class<?> cl, boolean all) {

            try {
            //創(chuàng)建流對(duì)象
                entry = new ObjectStreamClass(cl);
            } catch (Throwable th) {
                entry = th;
            }
}
  1. 創(chuàng)建流對(duì)象,尋找到要序列化對(duì)象中的私有方法,得到writeObjectMethod和readObjectMethod
private ObjectStreamClass(final Class<?> cl) {

//獲取對(duì)象中的私有方法,writeObject和readObject
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
}
  1. 在執(zhí)行寫(xiě)序列化數(shù)據(jù)時(shí)調(diào)用到方法:writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    //執(zhí)行到了創(chuàng)建好流對(duì)象的方法
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }
}
  1. invokeWriteObject調(diào)用到了writeObjectMethod.invoke
void invokeWriteObject(Object obj, ObjectOutputStream out)
        throws IOException, UnsupportedOperationException
    {
    if (writeObjectMethod != null) {
            try {
            //通過(guò)反射執(zhí)行了要序列化對(duì)象的writeObject方法
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

到這里我們就能看到我們要序列化對(duì)象的writeObject也被調(diào)用了,readObject過(guò)程分析也是一樣的。通過(guò)這樣我們可以想象,是不是我們就可以自己實(shí)現(xiàn)這個(gè)方法來(lái)重寫(xiě)寫(xiě)數(shù)據(jù)呢?答案肯定是的,哈哈哈.

重寫(xiě)writeObject和readObject:
  1. 改isMan默認(rèn)不序列化
  2. 重寫(xiě)寫(xiě)和讀方法
public class User implements Serializable {

    private static final long serialVersionUID = 123L;

    private String name;
    private int age;
    private transient boolean isMan;
    private String address;

    public User(String name, int age, boolean isMan, String address) {
        this.name = name;
        this.age = age;
        this.isMan = isMan;
        this.address = address;
    }

    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;
    }

    public boolean isMan() {
        return isMan;
    }

    public void setMan(boolean man) {
        isMan = man;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMan=" + isMan +
                ", address='" + address + '\'' +
                '}';
    }

    /**
     * 重寫(xiě)寫(xiě)方法
     *
     * @param oos
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream oos) throws IOException {
        //調(diào)用默認(rèn)的序列化方法,如里面注釋一樣,可以把非靜態(tài)和非transient字段給序列化了
        oos.defaultWriteObject();
        //把isMan也序列化一下
        oos.writeBoolean(isMan);
        System.out.println("序列化成功");
    }

    /**
     * 重寫(xiě)讀方法
     *
     * @param ois
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private void readObject(ObjectInputStream ois) throws IOException,
            ClassNotFoundException {
        //調(diào)用默認(rèn)的反序列化方法,如里面注釋一樣,可以把非靜態(tài)和非transient字段給反序列化了
        ois.defaultReadObject();
        //讀取序列化的字段
        isMan = ois.readBoolean();
        System.out.println("反序列化成功");
    }
}

再進(jìn)行序列化和反序列化,就能看到isMan咱們也能夠正常寫(xiě)入和讀了。

序列化是持久化的一個(gè)必備品,記錄到這里大概對(duì)Serializable就更熟悉了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容