物件序列化與反序列化
物件序列化的目標是將物件儲存到磁碟中,或允許網路中直接傳輸物件。物件序列化允許把記憶體中的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的無引數構造器。