序列化:將java對象轉換為位元組序列的過程叫做序列化
還原序列化:將位元組對象轉換為java對象的過程叫做還原序列化
通常情況下,序列化有兩種用途:、
1) 把對象的位元組序列永久的儲存在硬碟中
2)在網路上傳輸對象的位元組序列
相應的API
java.io.ObjectOutputStream
writeObject(Object obj)
java.io.ObjectInputStream
readObject()
只有實現了Serializable或者Externalizable介面的類的對象才能夠被序列化。否則當調用writeObject方法的時候會出現IOException。
需要注意的是Externalizable介面繼承自Serializable介面。兩者的區別如下:
僅僅實現Serializable介面的類可應採用預設的序列化方式。比如String類。
假設有一個Customer類的對象需要序列化,如果這個類僅僅實現了這個介面,那麼序列化和還原序列化的方式如下:ObjectOutputStream採用預設的序列化方式,對於這個類的非static,非transient的執行個體變數進行序列化。ObjectInputStream採用預設的還原序列化方式,對於這個類的非static,非transient的執行個體變數進行還原序列化。
如果這個類不僅實現了Serializable介面,而且定義了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那麼將按照如下的方式進行序列化和還原序列化:ObjectOutputStream會調用這個類的writeObject方法進行序列化,ObjectInputStream會調用相應的readObject方法進行還原序列化。
實現Externalizable介面的類完全由自身來控制序列化的行為。而且必須實現writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那麼將按照如下的方式進行序列化和還原序列化:ObjectOutputStream會調用這個類的writeExternal方法進行序列化,ObjectInputStream會調用相應的readExternal方法進行還原序列化。
下面來看一個最簡單的例子:
package com.java;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class simpleSerializableTest {public static void main(String[] args) throws Exception {ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));String strObj="name";Customer customer=new Customer("rollen");//序列化,此處故意將同一對象序列化2次out.writeObject(strObj);out.writeObject(customer);out.writeObject(customer);out.close();//還原序列化ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));String strobj1=(String)in.readObject();Customer cus1=(Customer)in.readObject();Customer cus2=(Customer)in.readObject();
in.close();System.out.println(strobj1+": "+cus1);System.out.println(strObj==strobj1);System.out.println(cus1==customer);System.out.println(cus1==cus2);}}class Customer implements Serializable {private static final long serialVersionUID = 1L;private String name;public Customer() {System.out.println("無參構造方法");}public Customer(String name) {System.out.println("有參構造方法");this.name = name;}public String toString() {return "[ "+name+" ]";}}
輸出結果為:
有參構造方法
name: [ rollen ]
false
false
true
可以看出,在進行反序列話的時候,並沒有調用類的構造方法。而是直接根據他們的序列化資料在記憶體中建立新的對象。另外需要注意的是,如果由一個ObjectOutputStream對象多次序列化同一個對象,那麼右一個objectInputStream對象還原序列化後的也是同一個對象。(cus1==cus2結果為true可以看出)
看一段代碼,證明static是不會被序列化的:
package com.java;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;import java.net.ServerSocket;import java.net.Socket;public class SerializableServer {public void send(Object obj) throws IOException {ServerSocket serverSocket = new ServerSocket(8000);while (true) {Socket socket = serverSocket.accept();ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());out.writeObject(obj);out.writeObject(obj);out.close();socket.close();}}public static void main(String[] args) throws Exception {Customer customer = new Customer("rollen", "male");new SerializableServer().send(customer);}}class Customer implements Serializable {private static final long serialVersionUID = 1L;private String name;private static int count;private transient String sex;static {System.out.println("調用靜態代碼塊");}public Customer() {System.out.println("無參構造方法");}public Customer(String name, String sex) {System.out.println("有參構造方法");this.name = name;this.sex = sex;count++;}public String toString() {return "[ " + count + " " + name + " " + sex + " ]";}}
package com.java;import java.io.ObjectInputStream;import java.net.Socket;public class SerializableClient {public void recive() throws Exception {Socket socket = new Socket("localhost", 8000);ObjectInputStream in = new ObjectInputStream(socket.getInputStream());Object obj1 = in.readObject();Object obj2 = in.readObject();System.out.println(obj1);System.out.println(obj1==obj2);}public static void main(String[] args) {try {new SerializableClient().recive();} catch (Exception e) {e.printStackTrace();}}}
運行結果中,count的值為0.
我們來看另外一種情況:
class A implements Serializable{B b;//...}class B implements Serializable{//...}
當我們在序列化A的對象的時候,也會自動序列化和他相關聯的B的對象。也就是說在預設的情況下,對象輸出資料流會對整個對象圖進行序列化。因此會導致出現下面的問題,看代碼(例子中是使用雙向列表作為內部結構的,只是給出了demo,並沒有完整的實現,只是為了說明情況):
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SeriListTest implements Serializable {private static final long serialVersionUID = 1L;private int size;private Node head = null;private Node end = null;private static class Node implements Serializable {private static final long serialVersionUID = 1L;String data;Node next;Node previous;}// 列表末尾添加一個字串public void add(String data) {Node node = new Node();node.data = data;node.next = null;node.previous = end;if (null != end) {end.next = node;}size++;end = node;if (size == 1) {head = end;}}public int getSize() {return size;}// other methods...public static void main(String[] args) throws Exception {SeriListTest list = new SeriListTest();for (int i = 0; i < 10000; ++i) {list.add("rollen" + i);}ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(list);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));list = (SeriListTest) in.readObject();System.out.println("size is :" + list.getSize());}}
這段代碼會出現如下錯誤:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
....
整個就是因為序列化的時候,對整個對象圖進行序列化引起的問題。在這種情況下啊,我們需要自訂序列化的過程:
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SeriListTest implements Serializable {private static final long serialVersionUID = 1L;transient private int size;transient private Node head = null;transient private Node end = null;private static class Node implements Serializable {private static final long serialVersionUID = 1L;String data;Node next;Node previous;}// 列表末尾添加一個字串public void add(String data) {Node node = new Node();node.data = data;node.next = null;node.previous = end;if (null != end) {end.next = node;}size++;end = node;if (size == 1) {head = end;}}public int getSize() {return size;}// other methods...private void writeObject(ObjectOutputStream outStream) throws IOException {outStream.defaultWriteObject();outStream.writeInt(size);for (Node node = head; node != null; node = node.next) {outStream.writeObject(node.data);}}private void readObject(ObjectInputStream inStream) throws IOException,ClassNotFoundException {inStream.defaultReadObject();int count = inStream.readInt();for (int i = 0; i < count; ++i) {add((String) inStream.readObject());}}public static void main(String[] args) throws Exception {SeriListTest list = new SeriListTest();for (int i = 0; i < 10000; ++i) {list.add("rollen" + i);}ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(list);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));list = (SeriListTest) in.readObject();System.out.println("size is :" + list.getSize());}}
運行結果為:10000
現在我們總結一下,在什麼情況下我們需要自訂序列化的方式:
1)為了確保序列化的安全性,對於一些敏感資訊加密
2)確保對象的成員變數符合正確的約束條件
3)最佳化序列化的效能(之前的那個例子已經解釋了這種情況)
下面我們來用例子解釋一下這些:
先來看看:為了確保序列化的安全性,對於一些敏感資訊加密
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SeriDemo1 implements Serializable {private String name;transient private String password; // 注意此處的transientpublic SeriDemo1() {}public SeriDemo1(String name, String password) {this.name = name;this.password = password;}// 此處類比對密碼進行加密,進行了簡化private String change(String password) {return password + "rollen";}private void writeObject(ObjectOutputStream outStream) throws IOException {outStream.defaultWriteObject();outStream.writeObject(change(password));}private void readObject(ObjectInputStream inStream) throws IOException,ClassNotFoundException {inStream.defaultReadObject();String strPassowrd = (String) inStream.readObject();//此處類比對密碼解密password = strPassowrd.substring(0, strPassowrd.length() - 6);}@Overridepublic String toString() {return "SeriDemo1 [name=" + name + ", password=" + password + "]";}public static void main(String[] args) throws Exception {SeriDemo1 demo = new SeriDemo1("hello", "1234");ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));demo = (SeriDemo1) in.readObject();System.out.println(demo);}}
然後我們看看:確保對象的成員變數符合正確的約束條件。比如一般情況下我們會在建構函式中對於參數進行合法性檢查,但是預設的序列化並不會調用類的建構函式,直接由對象的序列化資料來構造出一個對象,這個我們就有可能提供遺傳非法的序列化資料,來構造一個不滿足約束條件的對象。
為了避免這種情況,我們可以自訂反序列話的方式。比如在readObject方法中,進行檢查。當資料不滿足約束的時候(比如年齡小於0等等不滿足約束的情況),可以拋出異常之類的。
接下來我們看看readResolve()方法在單例模式中的使用:
單例模式大家應該都清楚,我就不多說了,看看下面的代碼:
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class ReadResolveDemo implements Serializable {private static final long serialVersionUID = 1L;private ReadResolveDemo() {}public static ReadResolveDemo getInstance() {return new ReadResolveDemo();}public static void main(String[] args) throws Exception {ReadResolveDemo demo=ReadResolveDemo.getInstance();ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();System.out.println(demo==demo1); //false}}
本來單例模式中,只有一個執行個體,但是在序列化的時候,無論採用預設的方式,還是自訂的方式,在還原序列化的時候都會產生一個新的對象,所以上面的程式運行輸出false。
因此可以看出還原序列化打破了單例模式只有一個執行個體的約定,為了避免這種情況,我們可以使用readReslove:
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class ReadResolveDemo implements Serializable {private static final long serialVersionUID = 1L;private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();private ReadResolveDemo() {}public static ReadResolveDemo getInstance() {return INSTANCE;}private Object readResolve() {return INSTANCE;}public static void main(String[] args) throws Exception {ReadResolveDemo demo = ReadResolveDemo.getInstance();ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();System.out.println(demo == demo1); // true}}
最後我們簡單的說一下實現Externalizable介面。 實現Externalizable介面的類完全由自身來控制序列化的行為。而且必須實現writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。
注意在對實現了這個介面的對象進行還原序列化的時候,會先調用類的不帶參數的建構函式,這個和之前的預設還原序列化方式是不一樣的。
例子如下:
package com.java;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.Externalizable;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;public class ExternalizableDemo implements Externalizable {private String name;static {System.out.println("調用靜態代碼塊");}public ExternalizableDemo() {System.out.println("調用預設無參建構函式");}public ExternalizableDemo(String name) {this.name = name;System.out.println("調用有參建構函式");}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);}@Overridepublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {name = (String) in.readObject();}@Overridepublic String toString() {return "[" + name + "]";}public static void main(String[] args) throws Exception {ExternalizableDemo demo = new ExternalizableDemo("rollen");ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));demo = (ExternalizableDemo) in.readObject();System.out.println(demo);}}
輸出:
調用靜態代碼塊
調用有參建構函式
調用預設無參建構函式
[rollen]
參考資料:
1.java序列化進階認識