1. 程式人生 > >java中的序列化與反序列化

java中的序列化與反序列化

序列化與反序列化

一 、什麼是序列化與反序列化 把物件轉換為位元組序列的過程稱為物件的序列化。 把位元組序列恢復為物件的過程稱為物件的反序列化

  • 一個物件只要實現了Serilizable介面,這個物件就可以被序列化,java的這種序列化模式為開發者提供了很多便利,我們可以不必關係具體序列化的過程,只要這個類實現了Serilizable介面,這個類的所有屬性和方法都會自動序列化。

為什麼需要實現Serilizable介面?

  • 如果一個類想被序列化,需要實現Serializable介面。否則將丟擲NotSerializableException異常,這是因為,在序列化操作過程中會對型別進行檢查,要求被序列化的類必須屬於Enum、Array和Serializable型別其中的任何一種,這也是為什麼Serializable雖然是一個空介面,但是隻要實現了該介面就能序列化和反序列化。

二、為什麼需要序列化與反序列化?

  • 當兩個程序進行遠端通訊時,可以相互發送各種型別的資料,包括文字、圖片、音訊、視訊等, 而這些資料都會以二進位制序列的形式在網路上傳送。當兩個Java程序進行通訊時,能否實現程序間的物件傳送呢?如何做到呢?此時就需要Java序列化與反序列化了!傳送方需要把這個Java物件轉換為位元組序列,然後在網路上傳送。接收方需要從位元組序列中恢復出Java物件。

序列化的好處

  • 實現了資料的持久化,通過序列化可以把資料永久地儲存到硬碟上(通常存放在檔案裡)
  • 用序列化實現遠端通訊,即在網路上傳送物件的位元組序列。

三、序列化特點

  1. 序列化時,只對物件的狀態進行儲存,而不管物件的方法。
  2. 當一個父類實現序列化時,子類自動實現序列化,不需要顯示實現Serializable介面
  3. 當一個物件的例項變數引用了其他物件時,序列化該物件時,也把引用物件序列化(可以理解成,在這裡引用物件只是當做類的屬性,無特殊說明自然會把屬性序列化)
  4. 物件中,被static或transient(物件的臨時資料)修飾的變數,不會被序列化。

四、如何序列化和反序列化 利用ObjectInputStream(物件輸入流)和ObjectOutputStream(物件輸出流)

  1. 序列化:ObjectInputStream(物件輸入流)
FileOutputStream fileOutputStream=new FileOutputStream(資料儲存目錄);
ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);//物件輸入流,得到的物件序列化,把得到的位元組序列寫到目標輸入流中
out.writeObject(序列化物件); //通過物件輸入流的writeObject方法寫物件

例子:Employee.java

//一個物件可序列化,需要實現Serilizable介面
public class Employee implements Serializable {
    public String name;
    public static String address;
    public transient String password;
    public int number;
    public Employee(String name,String address,String password,int number)
    {
        this.name=name;
        this.address=address;
        this.password=password;
        this.number=number;
    }
}

序列化類 SerializeDemo.java

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializeDemo {
    public static void main(String[] args)
    {
        Employee employee=new Employee("xiao","shenqiu","12345",1);
        try {
            FileOutputStream fileOutputStream=new FileOutputStream("/object.txt");
            ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);
            try {
                out.writeObject(employee);
            }
            catch (Exception ee)
            {
                ee.printStackTrace();
            }
            out.close();
            fileOutputStream.close();
            System.out.println("序列化資料已儲存在檔案中");
        }
        catch (IOException i)
        {
            i.printStackTrace();
        }
    }
}

序列化類執行結果:

序列化資料已儲存在檔案中

Process finished with exit code 0

用UltraEdit 開啟obiect.txt檔案可以看到檔案內容。物件已經序列化成位元組序列儲存在檔案中了。其中***h代表行。 在這裡插入圖片描述

  • 2.反序列化

利用物件輸入流ObjectInputStream

FileInputStream fileIn = new FileInputStream(資料讀取目錄);
ObjectInputStream in = new ObjectInputStream(fileIn);// 物件輸入流
e = (Employee) in.readObject();//通過物件輸入流的readObject()方法讀取物件
反序列化檔案 DeserializeDemo.java

import java.io.*;
public class DeserializeDemo {
    public static void main(String [] args)
    {
        Employee e = null;
        try
        {
            FileInputStream fileIn = new FileInputStream("/object.txt");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            e = (Employee) in.readObject();
            in.close();
            fileIn.close();
        }catch(Exception i)
        {
            i.printStackTrace();
            return;
        }
        System.out.println("Deserialized Employee...");
        System.out.println("Name: " + e.name);
        System.out.println("Address: " + e.address);
        System.out.println("Password: " + e.password);
        System.out.println("Number: " + e.number);
    }
}

反序列化執行結果:

Deserialized Employee...
Name: xiao
Address: null
Password: null
Number: 1

Process finished with exit code 0

這裡可以看到Employee 的屬性(static)Address和(transient)Password沒有被序列化,是null。 五、Serialversion 序列化版本號 (1)提高執行效率。如果在類中沒有顯式宣告Serialversion。那麼在序列化的時候回通過計算得到該值。顯式宣告可以省略計算。 (2)如果類沒有提供SerialversionUID,那麼編譯器會自動生成。SerialversionUID就是物件的hashcode。如果加入新的成員變數,那麼重新生成的SerialversionUID會變化。這樣反序列的時候crash,產生java.io.InvalidClassException異常,不能正常的反序列化。

例子: 我們在Employee.java中加一個屬性 i,再次執行反序列化檔案DeserializeDemo.java

Employee .java

import java.io.Serializable;
public class Employee implements Serializable {
    public String name;
    public static String address;
    public transient String password;
    public int number;
    public int i=0;//新加屬性i
    public Employee(String name,String address,String password,int number)
    {
        this.name=name;
        this.address=address;
        this.password=password;
        this.number=number;
    }
}

執行結果:

java.io.InvalidClassException: 序列化.Employee;local class incompatible:
stream classdesc serialVersionUID = -7599142119717411520, 
local class serialVersionUID = 5676476655507316830

可以看到序列化版本號不一致,導致不能反序列化,所以最好指定serialVersionUID。 我們指定serialVersionUID,看一下執行結果。 我們在Employee .java中指定serialVersionUID,執行SerializeDemo.java序列化,然後在檔案Employee.java中新增屬性 i,看一下反序列化的結果.

  1. 指定serialVersionUID。即修改Employee .java
  2. 序列化。即執行SerializeDemo .java
  3. 修改物件屬性。即修改Employee.java
  4. 反序列化。DeserializeDemo.java
Employee .java

import java.io.Serializable;
public class Employee implements Serializable {
    public String name;
    public static String address;
    public transient String password;
    public int number;
    public int i=0;   //新加屬性i
    private static final long serialVersionUID = -8976532647273106745L;//指定serialVersionUID
    public Employee(String name,String address,String password,int number)
    {
        this.name=name;
        this.address=address;
        this.password=password;
        this.number=number;
    }
}

執行DeserializeDemo.java檔案

Deserialized Employee...
Name: xiao
Address: null
Password: null
Number: 1

可以看到此時反序列化正確。指定serialVersionUID ,那麼修改序列化物件屬性,反序列化時也不會出錯。