1. 程式人生 > >java 序列化和反序列化詳解

java 序列化和反序列化詳解

1、什麼是序列化?為什麼要序列化?

Java 序列化就是指將物件轉換為位元組序列的過程,而反序列化則是隻將位元組序列轉換成目標物件的過程。

我們都知道,在進行瀏覽器訪問的時候,我們看到的文字、圖片、音訊、視訊等都是通過二進位制序列進行傳輸的,那麼如果我們需要將Java物件進行傳輸的時候,是不是也應該先將物件進行序列化?答案是肯定的,我們需要先將Java物件進行序列化,然後通過網路,IO進行傳輸,當到達目的地之後,再進行反序列化獲取到我們想要的物件,最後完成通訊。

2、如何實現序列化

2.1、使用到JDK中關鍵類 ObjectOutputStream 和ObjectInputStream

ObjectOutputStream 類中:通過使用writeObject(Object object) 方法,將物件以二進位制格式進行寫入。

ObjectInputStream 類中:通過使用readObject()方法,從輸入流中讀取二進位制流,轉換成物件。

2.2、目標物件需要先實現 Seriable介面

我們建立一個Student類:

public class Student implements Serializable {
    private static final long serialVersionUID = 3404072173323892464L;
    private
String name; private transient String id; private String age; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id='" + id + '\'' + ", age='" + age + '\'' + '}'; } public
String getAge() { return age; } public void setAge(String age) { this.age = age; } public Student(String name, String id) { System.out.println("args Constructor"); this.name = name; this.id = id; } public Student() { System.out.println("none-arg Constructor"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } }

程式碼中Student類實現了Serializable 介面,並且生成了一個版本號:

private static final long serialVersionUID = 3404072173323892464L;

首先:

1、Serializable 介面的作用只是用來標識我們這個類是需要進行序列化,並且Serializable 介面中並沒有提供任何方法。

2、serialVersionUid 序列化版本號的作用是用來區分我們所編寫的類的版本,用於判斷反序列化時類的版本是否一直,如果不一致會出現版本不一致異常。

3、transient 關鍵字,主要用來忽略我們不希望進行序列化的變數

2.3、將物件進行序列或和反序列化

2.3.1 第一種寫入方式: 
      

public static  void main(String[] args){
        File file = new File("D:/test.txt");
        Student student = new Student("孫悟空","12");
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
            outputStream.writeObject(student);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            Student s = (Student) objectInputStream.readObject();
            System.out.println(s.toString());
            System.out.println(s.equals(student));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

建立物件Student ,然後通過ObjectOutputStream類中的writeObject()方法,將物件輸出到檔案中

然後通過ObjectinputStream 類中的readObject()方法反序列化,獲取物件。

2.3.2 第二種寫入方式:

在Student 類中實現writeObject()和readObject()方法:

private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
        objectOutputStream.defaultWriteObject();
        objectOutputStream.writeUTF(id);

    }

    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        objectInputStream.defaultReadObject();
        id = objectInputStream.readUTF();
    }

通過這中方式進行序列話,我們可以自定義想要進行序列化的變數,將輸入流和輸出流傳入物件例項中,然後進行序列化以及反序列化。

2.3.3 第三種寫入方式:

Student 實現 Externalnalizable介面 而不實現Serializable 介面

Externaliable 介面是 Serializable 的子類,有著和Serializable介面同樣的功能:

public class Student implements Externalizable {
    private static final long serialVersionUID = 3404072173323892464L;
    private String name;
    private transient String id;
    private String age;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", age='" + age + '\'' +
                '}';
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public Student(String name, String id) {
        System.out.println("args Constructor");
        this.name = name;
        this.id = id;
    }

    public Student() {
        System.out.println("none-arg Constructor");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }


    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeObject(id);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        id = (String) in .readObject();
    }

}

通過和前面的第二種寫入方法對比,我們可以發現他們的實現原理都是十分的類似,不過實現Externalnalizable介面 並不支援第一種序列化方法,它只能夠通過實現介面中的writeExternal()和readExternal()方法實現物件的序列化。

3、面試中關於序列化的問題:

1、什麼是序列化,如何實現序列化

java中物件的序列化就是將物件轉換成二進位制序列,反序列化則是將二進位制序列轉換成物件

Java 實現序列化有多種方式

1、首先需要使用到工具類ObjectInputStream 和ObjectOutputStream 兩個IO類

2、實現Serializable 介面:

有兩種具體序列化方法:

直接通過ObjectOutputStream 和 ObjectInputStream 類中的 writeObject()和readObject()方法

通過在序列化物件中實現writeObject()和readObject()方法,傳入ObjectOutputStream和ObjectInputStream物件,完成序列化 

3、實現Externalizable 介面: 
只能夠通過實現介面中的writeExternal()和readExternal()方法實現物件的序列化

2、transient 關鍵字?如何將transient修飾符修飾的變數序列化? 
transient 的作用是用來遮蔽我們不希望進行序列化的變數,是物件在進行序列化和反序列話的過程中
忽略該變數。我們可以通過上述序列化方法中的 實現writeObject 和readObject 方法,在方法中呼叫輸出流或輸入流的writeUTF()和readUTF()方法。或者通過實現Externalizable 介面,實現writeExternal()和readExternal()方法,然後再自定義序列話物件。

      
3、如何保證序列化和反序列化後的物件一致?(如有異議望指正) 
對於這個問題我在查閱了一些資料之後,發現並不能保證序列化和反序列化之後的物件是一致的,因為我們在反序列化的過程中,是先建立一個物件,然後再通過對物件進行賦值來完成物件的反序列化,這樣問題就來了,在建立了一個新的物件之後,物件引用和原本的物件並不是指向同一個目標。因此我們只能保證他們的資料和版本一致,並不能保證物件一致。