本文主要從如下方面記錄:html
一、Java序列化和反序列化是什麼?java
二、爲何須要序列化與反序列化?web
三、怎麼實現Java序列化和反序列化?windows
四、幾個序列化注意事項api
通俗的來說,序列化過程就是將對象轉成二進制流存入內存或者文件,反序列化從內存或文件中讀取二進制流轉換成對象。數組
其實這個問題也就是它們的應用場景有哪些?這樣就容易回答多了。好比文件(文本、圖片等)進行傳輸,這些文件都是經過二進制序列的形式進行傳輸的(序列化過程),而接收方則要讀取這些二進制數據進行相對應的轉換(反序列化過程)。除了這個它主要用於網絡傳輸(進程之間的通訊等)。網絡
要想實現序列化有個必要條件就是要實現Serializable接口或Externalizable接口。大部分可能只知道有Serializable接口沒有關注Externalizable接口,那麼你看了本文以後就應該知道了,後面再介紹它們的區別。oracle
有了上面個必要條件後還須要藉助jdk中有兩個類:java.io.ObjectOutputStream和java.io.ObjectInputStream,它們分別負責序列化和反序列化。咱們能夠看下這兩個類的說明就知道是這兩個類負責相對應的功能。app
/** * An ObjectOutputStream writes primitive data types and graphs of Java objects * to an OutputStream. The objects can be read (reconstituted) using an * ObjectInputStream. Persistent storage of objects can be accomplished by * using a file for the stream. If the stream is a network socket stream, the * objects can be reconstituted on another host or in another process. * * <p>Only objects that support the java.io.Serializable interface can be * written to streams. The class of each serializable object is encoded * including the class name and signature of the class, the values of the * object's fields and arrays, and the closure of any other objects referenced * from the initial objects. * ... */ public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants{}
/** * An ObjectInputStream deserializes primitive data and objects previously * written using an ObjectOutputStream. * * <p>ObjectOutputStream and ObjectInputStream can provide an application with * persistent storage for graphs of objects when used with a FileOutputStream * and FileInputStream respectively. ObjectInputStream is used to recover * those objects previously serialized. Other uses include passing objects * between hosts using a socket stream or for marshaling and unmarshaling * arguments and parameters in a remote communication system. * ... */ public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants{ }
下面咱們以學生對象爲例,將對象序列化保存至文件中,再從文件中反序列化轉換成對象。socket
import java.io.Serializable; /** * @Description: 學生類 已實現序列化接口 * @author yuanfy * @date 2018年1月11日 上午11:36:37 * @version 1.0 */ public class Student implements Serializable{ private static final long serialVersionUID = 6415983562512521049L; private String name; private int age; private String sex; public Student(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } 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; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } }
上面已經定義Java對象, 下面進行序列化測試。
@Test public void testSerializable() throws FileNotFoundException, IOException{ Student s = new Student("james", 31, "man"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\Student.txt"))); oos.writeObject(s); oos.close(); }
若是電腦E盤存在的話運行確定經過(windows系統),單元測試經過後(說明序列化過程已完成)查看Student.txt文件中的內容,以下:
上面是正常的狀況, 若是Student類沒有實現Serializable接口呢?那麼序列化時會存在什麼樣的問題。咱們把Student類去掉Serializable接口的實現,而後再進行序列化測試。這時你會發現單元測試後不經過,先看看報錯緣由:下面截圖中的錯誤信息提示Student類沒有被序列化。
那麼爲何java.io.ObjectOutputStream類會拋出這樣的異常呢,它是怎麼識別有沒有實現Serializable接口的。接下來,咱們來看看他的源碼:writeObject方法中調用了writeObject()方法,因此主要邏輯在它裏面。
/** * Underlying writeObject/writeUnshared implementation. */ private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { //前面部分代碼省略 // remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) {//判斷是不是Serializable的子類。 writeOrdinaryObject(obj, desc, unshared); } else { //除了String類型、數組類型和枚舉類型,其餘對象若是沒有實現Serializable接口,都會拋出NotSerializableException異常 if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
從中能夠看出,除了String類型、數組類型和枚舉類型,其餘對象若是沒有實現Serializable接口,都會拋出NotSerializableException異常。
接下來進行反序列化測試:
@Test public void testDeserialize() throws FileNotFoundException, IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\Student.txt"))); Student s = (Student)ois.readObject(); System.out.println(s); ois.close(); }
在進行正常的序列化測試後接着測試反序列化測試,正常來講是不會報錯的。看看測試結果就明瞭了。
一樣上面是正常的狀況。接下來異常的狀況就能體現出Student類中serialVersionUID變量的做用了:它就是驗證序列化與反序列化的惟一性。在序列化後修改Student類中的serialVersionUID= 61234L,而後再測試反序列化,測試結果以下:
從錯誤提示中能夠看出,會從解析出來的serialVersionUID和要轉換的class中serialVersionUID作對比,判斷是否相等。java.io.ObjectStreamClass關鍵源碼以下:
/** * Initializes class descriptor representing a non-proxy class. */ void initNonProxy(ObjectStreamClass model, Class<?> cl, ClassNotFoundException resolveEx, ObjectStreamClass superDesc) throws InvalidClassException { long suid = Long.valueOf(model.getSerialVersionUID()); ObjectStreamClass osc = null; if (cl != null) { osc = lookup(cl, true); if (osc.isProxy) { throw new InvalidClassException( "cannot bind non-proxy descriptor to a proxy class"); } if (model.isEnum != osc.isEnum) { throw new InvalidClassException(model.isEnum ? "cannot bind enum descriptor to a non-enum class" : "cannot bind non-enum descriptor to an enum class"); } //這裏會判斷serialVersionUID是否一致 if (model.serializable == osc.serializable && !cl.isArray() && suid != osc.getSerialVersionUID()) { throw new InvalidClassException(osc.name, "local class incompatible: " + "stream classdesc serialVersionUID = " + suid + ", local class serialVersionUID = " + osc.getSerialVersionUID()); } //後面代碼略 } }
public class Person { public int age; public int weight; public Person(int age, int weight) { this.age = age; this.weight = weight; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } }
import java.io.Serializable; public class Women extends Person implements Serializable{ private static final long serialVersionUID = -1259423203325949704L; private String name; public Women(String name, int age, int weight) { super(age, weight); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", weight=" + weight + "]"; } }
單元測試代碼以下,先猜錯下會不會運行經過,結果又是啥?
@Test public void test2() throws FileNotFoundException, IOException, ClassNotFoundException{ Women w = new Women("Scarlett Johansson", 34, 50); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\Women.txt"))); oos.writeObject(w); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\Women.txt"))); Women s = (Women)ois.readObject(); System.out.println(s); ois.close(); }
測試結果以下,竟然報錯。該錯誤提示須要提供無參構造函數(During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.),具體詳情解釋見這here.
當咱們提供無參構造函數後再進行單元測試獲得結果:Student [name=Scarlett Johansson, age=0, weight=0]。因此能夠得出結論:父類中的字段不參與序列化,只是將其初始化而已。
使用transient關鍵字來修飾變量,而後進行序列化(在實現serializable接口狀況下)效果其實和父類的序列化同樣,它所修飾的變量不參與序列化。這裏就不舉例說明了,本身能夠寫個案例測試下。
另外transient關鍵字只能修飾變量, 不能修飾類和方法。
三、static關鍵字
使用static關鍵字來修飾變量,無論有沒有transient修飾,一樣不參與序列化(在實現serializable接口狀況下)。
四、Externalizable接口
Externalizable接口extends Serializable接口,並且在其基礎上增長了兩個方法:writeExternal()和readExternal()。這兩個方法會在序列化和反序列化還原的過程當中被自動調用,以便執行一些特殊的操做。
下面看案例:
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; /** * @Description: 學生類 已實現序列化接口 * @author yuanfy * @date 2018年1月11日 上午11:36:37 * @version 1.0 */ public class Student implements Externalizable{ private static final long serialVersionUID = 61234L; private String name; private transient int age; private static String sex; //若是覆蓋了無參構造函數就必定要顯示聲明無參構造函數,跟父類序列化的案例同樣。 public Student(){} public Student(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } 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; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]"; } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub out.writeUTF(name); out.writeInt(age); out.writeObject(sex); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.name = in.readUTF(); this.age = in.readInt(); this.sex = (String)in.readObject(); } }
@Test public void test3() throws FileNotFoundException, IOException, ClassNotFoundException{ Student s1 = new Student("james", 31, "man"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\Student.txt"))); oos.writeObject(s1); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\Student.txt"))); Student s2 = (Student)ois.readObject(); System.out.println(s2); ois.close(); }
上面是正常完整案例,其中要注意的是,要序列化的類要提供無參構造函數,不然反序列化會報錯(When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.)詳解here。得出結論以下:
與Serizable對象不一樣,使用Externalizabled,就意味着沒有任何東西能夠自動序列化, 爲了正常的運行,咱們須要在writeExtenal()方法中將自對象的重要信息寫入,從而手動的完成序列化。對於一個Externalizabled對象,對象的默認構造函數都會被調用(包括哪些在定義時已經初始化的字段),而後調用readExternal(),在此方法中必須手動的恢復數據。這就說明在Externalizable接口下不論是transient或static修飾的變量,若是沒有指定寫入,就不會序列化。