1. 程式人生 > >物件序列化與反序列化

物件序列化與反序列化

        物件序列化的目標是將物件儲存到磁碟中,或允許網路中直接傳輸物件。物件序列化允許把記憶體中的Java物件轉換為平臺無關的二進位制流,從而允許把這種二進位制流持久地儲存在磁碟上,通過網路將這種二進位制流傳輸到另一個網路節點。而其它程式獲得了這種二進位制流,都可以用反序列化將二進位制流恢復成原來的Java物件。

1.使用物件流實現序列化

        如果需要將某個物件序列化,這個類應該實現Serializable介面或者Externalizable介面之一。這裡首先介紹實現Serializable介面,只需要待序列化的類實現該介面即可。可看如下程式:

Person類:

import java.io.Serializable;

public class Person implements Serializable {
	public int age;
	public String name;
	
	public Person(int age,String name) {
		this.age=age;
		this.name = name;
	}
	
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	

}

序列化:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class Test1 {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		//序列化
		
		Person person = new Person(11, "Bob");
		ByteArrayOutputStream os = new ByteArrayOutputStream();//定義一個位元組陣列輸出流
		ObjectOutputStream outputStream = new ObjectOutputStream(os);//物件輸出流
		outputStream.writeObject(person);//將物件寫入到位元組陣列輸出,實現序列化。
		byte[] personByte = os.toByteArray();
		System.out.println(Arrays.toString(personByte));

	}

}

返回這個序列化後的位元組陣列:

[-84, -19, 0, 5, 115, 114, 0, 15, 115, 101, 114, 105, 97, 98, 108, 101, 46, 80, 101, 114, 115, 111, 110, -84, -127, 59, -107, 84, -82, -111, -29, 2, 0, 2, 73, 0, 3, 97, 103, 101, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 120, 112, 0, 0, 0, 11, 116, 0, 3, 66, 111, 98]

2.反序列化

        將以上序列化之後再進行反序列化,恢復Java物件,程式碼如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class Test {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		//序列化
		
		Person person = new Person(11, "Bob");
		ByteArrayOutputStream os = new ByteArrayOutputStream();//定義一個位元組陣列輸出流
		ObjectOutputStream outputStream = new ObjectOutputStream(os);//物件輸出流
		outputStream.writeObject(person);//將物件寫入到位元組陣列輸出,實現序列化。
		byte[] personByte = os.toByteArray();
		System.out.println(Arrays.toString(personByte));
		
		
		//反序列化
		ByteArrayInputStream is = new ByteArrayInputStream(personByte);//位元組組輸入流
		ObjectInputStream inputStream = new ObjectInputStream(is);
		Person person2 = (Person)inputStream.readObject();
		System.out.println(person2.name+":"+person2.age);
		
		
		
		
		

	}

}

執行結果:

[-84, -19, 0, 5, 115, 114, 0, 15, 115, 101, 114, 105, 97, 98, 108, 101, 46, 80, 101, 114, 115, 111, 110, -84, -127, 59, -107, 84, -82, -111, -29, 2, 0, 2, 73, 0, 3, 97, 103, 101, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 120, 112, 0, 0, 0, 11, 116, 0, 3, 66, 111, 98]
Bob:11

3.序列化檔案儲存唯一性

        即一個類多次序列化,但只有一個其對應的序列化儲存區,無論其被多少個對應引用,都只有一個結果 。具體示例如下,首先將兩個相同Person物件寫入檔案,然後反序列化出兩個物件,進行比較:


import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class Test1 {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		//序列化
		
		Person person = new Person(11, "Bob");
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
		oos.writeObject(person);
		oos.writeObject(person);
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
		Person p1 = (Person)ois.readObject();
		Person p2 = (Person)ois.readObject();
		System.out.println(p1==p2);

	}

}

執行結果:

true

        以上結果說明:如果多次序列化同一個Java物件時,只有第一次序列化時才會把該Java物件轉換成位元組序列並輸出。但這也有一定問題,當程式序列化一個可變物件時,只有第一次使用writeObject()方法輸出時才會將該物件轉換成位元組序列並輸出,當程式再次呼叫writeObject()方法時,程式只是輸出前面的序列化編號,即使後面該物件的例項變數已被改變,改變的例項變數也不會輸出。例子如下:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class Test1 {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		//序列化
		
		Person person = new Person(11, "Bob");
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
		oos.writeObject(person);
		person.setAge(12);
		oos.writeObject(person);
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
		Person p1 = (Person)ois.readObject();
		
		Person p2 = (Person)ois.readObject();
		System.out.println(p2.age);
		System.out.println(p1==p2);

	}

}

執行結果:

11
true

以上結果說明,即使age變數發生了改變,但兩次person物件只認準第一次writeObject()方法的序列化結果。

4.關鍵字transient

        transient修飾的例項變數在序列化過程中將被完全隔離在序列化機制之外,從而導致在反序列化恢復Java物件時無法取得該例項變數值。如下例子所示:

import java.io.Serializable;

public class Person implements Serializable {
	public int age;
	public String name;
	public transient int height;
	
	public Person(int age,String name) {
		this.age=age;
		this.name = name;
	}
	public Person(int age,String name,int height) {
		this.age = age;
		this.name = name;
		this.height = height;
	}
	
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	

}

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TransientTest {

	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));
		Person person = new Person(11,"Tom",180);
		oos.writeObject(person);
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt"));
		Person person2 = (Person)ois.readObject();
		System.out.println(person2.age);
		System.out.println(person2.height);

	}

}

執行結果如下:

11
0

5.自定義序列化機制-Externalizable介面

        該介面繼承了Serializable介面,並且擴充套件了自定義序列化類的功能。

從下圖可以看出,實現Externalizable介面,需要實現兩個方法:readExternal()和writeExternal(),分別對應著序列化類的反序列化和序列化操作。

        根據原始碼上註釋可知:

void writeExternal(ObjectOutput out) throws IOException:需要序列化的類實現writeExternal()方法來儲存物件的狀態。該方法呼叫DataOutput的方法來儲存基本型別的例項變數值,呼叫ObjectOutput的writeObject()方法來儲存引用型別的例項變數值。

void readExternal(ObjectInput in) throws IOException, ClassNotFoundException:需要序列化的類實現該方法來實現反序列化。該方法呼叫DataInput的方法來恢復基本型別的例項變數值,呼叫ObjectInput的readObject()方法來恢復引用型別的例項變數值。

以下是程式碼示例:

Person類:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import com.sun.org.apache.bcel.internal.generic.NEW;

public class Person implements Externalizable {
	public String name;
	public int age;
	
	public Person() {}
	public Person(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;
	}

	//通過該方法來儲存物件的狀態。該方法呼叫DataOutput的方法來儲存基本型別的例項變數值,呼叫ObjectOutput的writeObject()方法來儲存引用型別的例項變數值,呼叫ObjectOutput的writeObject()方法來儲存引用型別的例項變數值
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		// TODO Auto-generated method stub
		out.writeObject(new StringBuilder(name).reverse());
		out.writeInt(age);

	}

	//需要序列化的類實現該方法來實現反序列化。該方法呼叫DataInput的方法來恢復基本型別的例項變數值,呼叫ObjectInput的readObject()方法來恢復引用型別的例項變數值
	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		this.name = ((StringBuilder)(in.readObject())).reverse().toString();
		this.age = in.readInt();

	}

}

 測試類:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;



public class Test {

	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		// TODO Auto-generated method stub
		//序列化
		Person person = new Person("Bob",12);
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
		oos.writeObject(person);
		person.setAge(13);
		oos.writeObject(person);
		
		
		//反序列化
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
		Person person2 = (Person)ois.readObject();
		Person person3 = (Person)ois.readObject();
		System.out.println(person2.name+":"+person2.age);
		System.out.println(person2==person3);
		
		
		
 		

	}

}

執行結果:

Bob:12
true

注意:實現Externalizable介面的類反序列化時,程式會先使用public的無引數構造器建立例項,然後執行readExternal()方法進行反序列化。因此實現Externalizable介面的類必須提供一個public的無引數構造器。