java的序列化 和 反序列化總結---學習筆記

  java的序列化 和 反序列化

1、我們先看一下《java編程思想》第四版中對序列化定義

對象序列化
Java 1.1 增添了一種有趣的特性,名爲“對象序列化”( Object Serialization)。它面向那些實現了
Serializable 接口的對象,可將它們轉換成一系列字節,並可在以後完全恢復回原來的樣子。這一過程亦可
通過網絡進行。這意味着序列化機制能自動補償操作系統間的差異。換句話說,可以先在Windows 機器上創
建一個對象,對其序列化,然後通過網絡發給一臺 Unix 機器,然後在那裏準確無誤地重新“裝配”。不必關
心數據在不同機器上如何表示,也不必關心字節的順序或者其他任何細節。
就其本身來說,對象的序列化是非常有趣的,因爲利用它可以實現“有限持久化”。請記住“持久化”意味
着對象的“生存時間”並不取決於程序是否正在執行—— 它存在或“生存”於程序的每一次調用之間。通過
序列化一個對象,將其寫入磁盤,以後在程序重新調用時重新恢復那個對象,就能圓滿實現一種“持久”效
果。之所以稱其爲“有限”,是因爲不能用某種“ persistent”(持久)關鍵字簡單地地定義一個對象,並
讓系統自動照看其他所有細節問題(儘管將來可能成爲現實)。相反,必須在自己的程序中明確地序列化和
組裝對象。
語言裏增加了對象序列化的概念後,可提供對兩種主要特性的支持。 Java 1.1 的“遠程方法調用”( RMI)
使本來存在於其他機器的對象可以表現出好象就在本地機器上的行爲。將消息發給遠程對象時,需要通過對
象序列化來傳輸參數和返回值。 RMI 將在第 15 章作具體討論。
對象的序列化也是 Java Beans 必需的,後者由 Java 1.1 引入。使用一個 Bean 時,它的狀態信息通常在設計
期間配置好。程序啓動以後,這種狀態信息必須保存下來,以便程序啓動以後恢復;具體工作由對象序列化
完成。
對象的序列化處理非常簡單,只需對象實現了 Serializable 接口即可(該接口僅是一個標記,沒有方法)。
在 Java 1.1 中,許多標準庫類都發生了改變,以便能夠序列化—— 其中包括用於基本數據類型的全部封裝
器、所有集合類以及其他許多東西。甚至 Class 對象也可以序列化(第 11 章講述了具體實現過程)。
爲序列化一個對象,首先要創建某些 OutputStream 對象,然後將其封裝到 ObjectOutputStream 對象內。此
時,只需調用 writeObject()即可完成對象的序列化,並將其發送給 OutputStream。相反的過程是將一個
InputStream 封裝到 ObjectInputStream 內,然後調用 readObject()。和往常一樣,我們最後獲得的是指向
一個上溯造型 Object 的句柄,所以必須下溯造型,以便能夠直接設置。
對象序列化特別“聰明”的一個地方是它不僅保存了對象的“全景圖”,而且能追蹤對象內包含的所有句柄
並保存那些對象;接着又能對每個對象內包含的句柄進行追蹤;以此類推。我們有時將這種情況稱爲“對象
網”,單個對象可與之建立連接。而且它還包含了對象的句柄數組以及成員對象。若必須自行操縱一套對象
序列化機制,那麼在代碼裏追蹤所有這些鏈接時可能會顯得非常麻煩。在另一方面,由於Java 對象的序列化
似乎找不出什麼缺點,所以請儘量不要自己動手,讓它用優化的算法自動維護整個對象網。

下面是序列化的小例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class Person implements Serializable {
	private static final long serialVersionUID = 1L;
	private String name;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString() {
		return "Person [name=" + name + "]";
	}
	public static void main(String[] args)  {
		Person p=new Person();
		p.setName("張三");
		try {
			
		File f=new File("d:/3.txt");
		if(!f.exists()){
			f.createNewFile();
		}
		
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));
		
		
		oos.writeObject(p);
		oos.flush();
		oos.close();
		
		} catch (Exception e) {
			e.printStackTrace();
			// TODO: handle exception
		}
		
		try {
			ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));
			
			Person pp=(Person)ois.readObject();
			System.out.println(pp);
			
		} catch (Exception e) {
			e.printStackTrace();
			// TODO: handle exception
		}
	
		
	}
	
	
}
結果爲:

Person [name=張三]

從上面的代碼中可以知道序列化對象讀取序列化對象過程中最要的兩個函數是OutStream的writeObject和InputStream的readObject ,這個序列化過程是默認的。當然如果有特殊要求可以自己定製序列化。下面是書中例子

2、序列化的控制
正如大家看到的那樣,默認的序列化機制並不難操縱。然而,假若有特殊要求又該怎麼辦呢?我們可能有特殊的安全問題,不希望對象的某一部分序列化;或者某一個子對象完全不必序列化,因爲對象恢復以後,那一部分需要重新創建。此時,通過實現 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具體過程。這個
Externalizable 接口擴展了 Serializable,並增添了兩個方法: writeExternal()和 readExternal()。在序
列化和重新裝配的過程中,會自動調用這兩個方法,以便我們執行一些特殊操作。
下面這個例子展示了 Externalizable 接口方法的簡單應用。注意 Blip1 和 Blip2 幾乎完全一致,除了極微小

的差別(自己研究一下代碼,看看是否能發現):

//: Blips.java
// Simple use of Externalizable & a pitfall
import java.io.*;
import java.util.*;

class Blip1 implements Externalizable {
    public Blip1() {
        System.out.println("Blip1 Constructor");
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip1.writeExternal");
    }

    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("Blip1.readExternal");
    }
}

class Blip2 implements Externalizable {
    Blip2() {
        System.out.println("Blip2 Constructor");
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Blip2.writeExternal");
    }

    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("Blip2.readExternal");
    }
}

public class Blips {
    public static void main(String[] args) {
        System.out.println("Constructing objects:");
        Blips b1 = new Blips();
        Blip2 b2 = new Blip2();
        try {
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                    "Blips.out"));
            System.out.println("Saving objects:");
            o.writeObject(b1);
            o.writeObject(b2);
            o.close();
            // Now get them back:
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                    "Blips.out"));
            System.out.println("Recovering b1:");
            b1 = (Blips) in.readObject();
            // OOPS! Throws an exception:
            // ! System.out.println("Recovering b2:");
            // ! b2 = (Blip2)in.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} // /:~

該程序輸出如下:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
未恢復 Blip2 對象的原因是那樣做會導致一個違例。你找出了 Blip1 和 Blip2 之間的區別嗎? Blip1 的構建
器是“公共的”( public), Blip2 的構建器則不然,這樣便會在恢復時造成違例。試試將 Blip2 的構建器
屬性變成“ public”,然後刪除//!註釋標記,看看是否能得到正確的結果。
恢復 b1 後,會調用 Blip1 默認構建器。這與恢復一個 Serializable(可序列化)對象不同。在後者的情況
下,對象完全以它保存下來的二進制位爲基礎恢復,不存在構建器調用。而對一個Externalizable 對象,所
有普通的默認構建行爲都會發生(包括在字段定義時的初始化),而且會調用readExternal()。必須注意這
一事實—— 特別注意所有默認的構建行爲都會進行— — 否則很難在自己的 Externalizable 對象中產生正確的
行爲。
下面這個例子揭示了保存和恢復一個 Externalizable 對象必須做的全部事情:

//: Blip3.java
// Reconstructing an externalizable object
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

class Blip3 implements Externalizable {
	int i;
	String s; // No initialization

	public Blip3() {
		System.out.println("Blip3 Constructor");
		// s, i not initialized
	}

	public Blip3(String x, int a) {
		System.out.println("Blip3(String x, int a)");
		s = x;
		i = a;
		// s & i initialized only in non-default
		// constructor.
	}

	public String toString() {
		return s + i;
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		System.out.println("Blip3.writeExternal");
		// You must do this:
		out.writeObject(s);
		out.writeInt(i);
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		System.out.println("Blip3.readExternal");
		// You must do this:
		s = (String) in.readObject();
		i = in.readInt();
	}

	public static void main(String[] args) {
		System.out.println("Constructing objects:");
		Blip3 b3 = new Blip3("A String ", 47);
		System.out.println(b3.toString());
		try {
			ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
					"Blip3.out"));
			System.out.println("Saving object:");
			o.writeObject(b3);
			o.close();
			// Now get it back:
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					"Blip3.out"));
			System.out.println("Recovering b3:");
			b3 = (Blip3) in.readObject();
			System.out.println(b3.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
} // /:~

其中,字段 s 和 i 只在第二個構建器中初始化,不關默認構建器的事。這意味着假如不在readExternal 中初
始化 s 和 i,它們就會成爲 null(因爲在對象創建的第一步中已將對象的存儲空間清除爲 1)。若註釋掉跟
隨於“ You must do this”後面的兩行代碼,並運行程序,就會發現當對象恢復以後, s 是 null,而 i 是
零。
若從一個 Externalizable 對象繼承,通常需要調用 writeExternal()和 readExternal()的基礎類版本,以便
正確地保存和恢復基礎類組件。
所以爲了讓一切正常運作起來,千萬不可僅在 writeExternal()方法執行期間寫入對象的重要數據(沒有默
認的行爲可用來爲一個 Externalizable 對象寫入所有成員對象)的,而是必須在 readExternal()方法中也
恢復那些數據。初次操作時可能會有些不習慣,因爲Externalizable 對象的默認構建行爲使其看起來似乎正
在進行某種存儲與恢復操作。但實情並非如此。

3、transient(臨時)關鍵字


控制序列化過程時,可能有一個特定的子對象不願讓Java 的序列化機制自動保存與恢復。一般地,若那個子
對象包含了不想序列化的敏感信息(如密碼),就會面臨這種情況。即使那種信息在對象中具有“ private”
(私有)屬性,但一旦經序列化處理,人們就可以通過讀取一個文件,或者攔截網絡傳輸得到它。
爲防止對象的敏感部分被序列化,一個辦法是將自己的類實現爲Externalizable,就象前面展示的那樣。這
樣一來,沒有任何東西可以自動序列化,只能在writeExternal()明確序列化那些需要的部分。
然而,若操作的是一個 Serializable 對象,所有序列化操作都會自動進行。爲解決這個問題,可以用
transient(臨時) 逐個字段地關閉序列化,它的意思是“不要麻煩你(指自動機制)保存或恢復它了—— 我
會自己處理的”。
例如,假設一個 Login 對象包含了與一個特定的登錄會話有關的信息。校驗登錄的合法性時,一般都想將數
據保存下來,但不包括密碼。爲做到這一點,最簡單的辦法是實現Serializable,並將 password 字段設爲
transient。有時候需要序列化的對象中的屬性包含有不能序列化的對象。例如下面的例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class Person implements Serializable {
	private static final long serialVersionUID = 1L;
	private String name;
	private Student student;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Student getStudent() {
		return student;
	}
	public void setStudent(Student student) {
		this.student = student;
	}
	
	@Override
	public String toString() {
		return "Person [name=" + name + ", student=" + student + "]";
	}
	public static void main(String[] args)  {
		Person p=new Person();
		p.setStudent(new Student("張三","4歲"));
		p.setName("學生");
		try {
			
		File f=new File("d:/3.txt");
		if(!f.exists()){
			f.createNewFile();
		}
		
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(f));
		
		
		oos.writeObject(p);
		oos.flush();
		oos.close();
		
		} catch (Exception e) {
			e.printStackTrace();
			// TODO: handle exception
		}
		
		try {
			ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/3.txt"));
			
			Person pp=(Person)ois.readObject();
			System.out.println(pp);
			
		} catch (Exception e) {
			e.printStackTrace();
			// TODO: handle exception
		}
	
		
	}
	
	
}

class Student {

	private String name;
	private String age;
	
	
	
	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public Student(String name, String age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAge() {
		return age;
	}
	public void setAge(String age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
	
	
}

執行結果報錯:

java.io.NotSerializableException: Student

這是由於Student 類不可以序列化, 在這種情況下,可以給Student 添加 transient 關鍵字,

    private transient Student student;

執行結果:

Person [name=學生, student=null]

如果Student是可以序列化的,Student類實現Serializable,去掉transient 關鍵字:

執行結果:

Person [name=學生, student=Student [name=張三, age=4歲]]

4 總結
序列化:將java對象轉換爲字節序列的過程叫做序列化

反序列化:將字節對象轉換爲java對象的過程叫做反序列化

通過實現 Externalizable 接口,用它代替 Serializable 接口,便可控制序列化的具體過程,這個
Externalizable 接口擴展了 Serializable,並增添了兩個方法: writeExternal()和 readExternal()。

transient 可以使某些屬性不被序列化,但是如果是實現了Externalizable接口的類中屬性添加 transient也是會被序列化的.



本文參考了<java編程思想>第四版 第10章第九節

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