Java基礎(五)-Java序列化與反序列化

本文主要從如下方面記錄:html

一、Java序列化和反序列化是什麼?java

二、爲何須要序列化與反序列化?web

三、怎麼實現Java序列化和反序列化?windows

四、幾個序列化注意事項api

1、Java序列化和反序列化是什麼?

  通俗的來說,序列化過程就是將對象轉成二進制流存入內存或者文件,反序列化從內存或文件中讀取二進制流轉換成對象。數組

  

2、爲何須要序列化與反序列化?

  其實這個問題也就是它們的應用場景有哪些?這樣就容易回答多了。好比文件(文本、圖片等)進行傳輸,這些文件都是經過二進制序列的形式進行傳輸的(序列化過程),而接收方則要讀取這些二進制數據進行相對應的轉換(反序列化過程)。除了這個它主要用於網絡傳輸(進程之間的通訊等)。網絡

3、怎麼實現Java序列化和反序列化?

  要想實現序列化有個必要條件就是要實現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());
            }
            //後面代碼略
        }
    }

 4、幾個序列化注意事項

  一、對繼承父類的子類序列化

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 關鍵字

   使用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修飾的變量,若是沒有指定寫入,就不會序列化。

相關文章
相關標籤/搜索