序列化:將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; // 注意此處的transient public 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); } @Override public 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("調用有參構造函數"); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); } @Override public 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]
參考資料: