java Serialization 相關問題

注:如果您發現任何不正確的內容,或者您想要分享有關本文主題的更多信息,請撰寫評論或聯繫我 [email protected]

以下整理文章參考《java編程思想第四版》《EffectiveJava》oracle java官網 以及部分博客

以下環境基於 windows 10 java version “1.8.0_171”

文中代碼可能不太嚴謹,直供研究測試使用!

http://zjlgd.cn 請關注我的網站,大家一起進步!

是什麼

​ java的序列化是java I/O流內容的一部分。

​ 序列化是將類的實例(類的對象)轉換爲字節流的過程。然後,此字節流可以作爲文件存儲在磁盤上,也可以通過網絡發送到另一臺計算機(使用另一臺JVM 實例化的對象)。

序列化對象時 僅會序列化 類的默認可序列化字段被定義爲非瞬態和非靜態字段。 也就是非 static以及非transient 修飾字段

​ 當程序關閉或者休眠時,使用 Serialization 將對象狀態保存在磁盤的某個文件上,當程序重新啓動時可以訪問該文件將對象重新new出來,這個過程可以稱之爲 反序列化。(術語可能不太標準,僅供理解)

當一個類從序列化狀態恢復時,反序列化會繞過構造函數包括無參構造。

爲什麼

從何而來?

​ java的對象必須在內存在才能工作,一旦斷電或者java程序中斷,java產生的所有對象都煙消雲散。

那麼爲什麼不能跳過內存,直接使用硬盤來操作對象?

1.  按照 馮·諾依曼體系要求,數據必須在內存中,cpu才能操作。
2.  磁盤速度相比內存和cpu是非常慢的。

json不是一樣可以達到持久性的效果嗎,爲什麼要用java serializable?

​ 將對象的值 轉爲 json類型格式存儲在文件,或者將對象值存儲與數據庫同樣可以達到 java 序列化,持久性的效果。java的序列化看你的選擇,功能就擺在那裏,用不用是你的事。用java自己寫的當然更加堅挺,持久!

​ 但是在使萬物都成爲對象的精神中,如果能夠將一個對象聲明爲是“持久性”的,併爲我們處理掉所有細節,那將會顯得十分方便。

---- 摘自《java編程思想第四版》 18.12

  • java序列話可以將對象轉化爲字節序列,且這個序列可以恢復爲原來的對象。在windows 實例化 的對象通過序列化將字節序列發給Unix系統,不必擔心數據在不同機器的不同表示,以及字節順序等等,java反序列化能夠準確的重新組裝。(支持RMI)
  • 利用java的序列化可以實現輕量級的持久性

還有等等·····

怎麼樣

注意:

如果父類已實現Serializable接口,則子類不需要實現它,反之亦然。

僅通過序列化過程保存非靜態數據成員(not static)

靜態數據成員和臨時數據成員(transient)不通過序列化過程保存。因此,如果您不想保存非靜態數據成員的值,則將其設置爲瞬態。

反序列化對象時,永遠不會調用對象的構造函數。

關聯對象必須實現Serializable接口。

import java.io.Serializable;

public class Box implements Serializable {

    private static final long serialVersionUID = 61485867982808238L;

    private int width;
    private int height;

    public Box(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public Box() {
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
    
    // 爲了方便觀察結果,重寫toString方法
    @Override
    public String toString() {
        return "Box{" +
                "width=" + width +
                ", height=" + height +
                '}';
    }
}
  • serialVersionUID 流的唯一標識符(序列版本) 適用於java序列化機制。簡單來說,JAVA序列化的機制是通過判斷類的serialVersionUID來驗證的版本一致的。
  • 在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID於本地相應實體類的serialVersionUID進行比較。如果相同說明是一致的,可以進行反序列化,否則會出現反序列化版本一致的異常,即是InvalidCastException。

我可以不寫serialVersionUID嗎?

建議是 顯式聲明serialVersionUID。並且不爲 1L

如果沒有顯式的聲明私有的,靜態的,最終的,long類型的serialVersionUID ,系統會自動根據這個類的相關數據,運行時生成標識。如果類的名稱或成員變量,實現繼承類有改變自動生成的標識也會改變。爲了避免這方面的問題,應該顯式聲明serialVersionUID,並且他應該是唯一的,而不是 1L

取自 《Effective Java》第二版

關於 版本id 不同導致的詳細情況 請參考 https://blog.csdn.net/u014750606/article/details/80040130

A 序列化 B 反序列化

  • 版本號不一致 —> 報錯 InvalidCastException
  • 版本號一致,A 多字段 —> A多 字段丟失
  • 版本號一致,B 少字段 —> A多 字段丟失.
  • 版本號一致,B 多字段 —> B 多字段被賦予默認值

serializable序列化

​ serializable 接口是標記接口。這意味着如果您的類派生自此接口,則不必實現任何方法。這只是一個標記,Java運行時,在嘗試序列化類時,只會檢查類中是否存在此接口。如果類繼承層次結構中存在Serializable接口,則Java運行時將負責類的序列化。 序列化我們需要執行以下幾個步驟

1. 創建一個文件的新 FileOutputStream,我們想要序列化該類到指定的文件
2. 創建 ObjectOutputStream 有參構造入參爲 第一步創建的FileOutputStream
3. 將對象寫入 ObjectOutputStream
4. 關閉所有流
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;


public class Test {

    public static void SerializeToFile(Object classObject, String fileName) {
        try {
        // 這裏的FileOutputStream 只是爲了方便觀察,將序列化後的結構寫入文件。
            FileOutputStream fileStream = new FileOutputStream(fileName);
		// ObjectOutputStream繼承OutputStream,序列化基於字節因要使用InputStream/OutputStream繼承		 // 層次結構
            ObjectOutputStream objectStream = new ObjectOutputStream(fileStream);

            objectStream.writeObject(classObject);

            objectStream.close();
            fileStream.close();

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Box rect = new Box(18, 78);
        // 我是windows 10 環境
        SerializeToFile(rect, "G:\\rectSerialized");

    }
}

執行下面這步將會將對象序列化

objectStream.writeObject(classObject);

在執行writeObject方法時,會校驗要序列化的類是否標記serializable,如果不是將會拋出 NotSerializableException。

// remaining cases
if (obj instanceof String) {
	writeString((String) obj, unshared);
} else if (cl.isArray()) {
	writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
	writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
	writeOrdinaryObject(obj, desc, unshared);
} else {
	if (extendedDebugInfo) {
		throw new NotSerializableException(
		cl.getName() + "\n" + debugInfoStack.toString());
	} else {
		throw new NotSerializableException(cl.getName());
	}
}

對象的序列化會對對象內包含的引用進行跟蹤,並且保存他。

執行main函數之後,在我的G盤根目錄下生成了一個 rectSerialized 文件,大小爲1KB,我嘗試使用notepad++打開,但是顯示的內容看不懂。。。

注意:

序列化只是序列化對象的變量(可序列化的),並不涉及方法

serializable反序列化

​ 反序列化即:將InputStream封裝在ObjectInputStream中,調用readObject(),得到的是一個向上轉型的 Object,需要向下轉型 強轉爲指定對象。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;


public class Test {

    public static Object DeSerializeFromFileToObject(String fileName) {
        try {

            FileInputStream fileStream = new FileInputStream(new File(fileName));

            ObjectInputStream objectStream = new ObjectInputStream(fileStream);

            Object deserializeObject = objectStream.readObject();

            objectStream.close();
            fileStream.close();

            return deserializeObject;

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        Box example =(Box)DeSerializeFromFileToObject("G:\\rectSerialized");
        System.out.println(example.toString());
    }
}

執行main函數結果如下:

Box{width=18, height=78}

Externalizable

序列化時會將本對象的某個子對象的某一部分進行序列化,這種情況可以實現Externalizable 接口。用法請自行參考。

誰用了Externalizable

哪個東西在哪裏使用了序列化

TODO

  1. 爲啥,數據必須在內存中,cpu才能操作。Why can’t CPUs get data directly from Hard drives?

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章