Java序列化與反序列化
Java提供了兩種對象持久化的方式,分別為序列化和外部序列化
-
-
序列化
-
在分布式環境下,當進行遠程通信時,無論是何種類型的數據,都會以二進制序列的形式在網絡上傳輸。序列化是一種將對象以一連串的字節描述的過程,用於解決在對對象流進行讀寫操作時所引發的問題。序列化可以將對象的狀態寫在流里進行網絡傳輸,或者保存到文件、數據庫等系統中,並在需要時把該流讀取出來重新構造一個相同的對象。
所有實現序列化的類都必須實現Serializable接口,Serializable接口位於java.lang包中,沒有任何實現方法,使用一個輸出流(例如FileOutputStream)來構造一個ObjectOutputStream對象,緊接着使用該對象的writeObject(Object obj)方法就可以將obj對象寫出,要恢復時可以使用其對應的輸入流ObjectInputStream.
序列化有以下兩個特點:
-
- 如果一個類能被序列化,那么它的子類也能夠被序列化。
- 由於static(靜態)代表類的成員,transient(Java關鍵字,如果用transient聲明一個實例變量,當對象存儲時,它的值不需要維持)代表對象的臨時數據,因此被聲明為這兩種類型的數據成員是不能夠被序列化的。
如下是序列化的代碼,首先聲明一個Student類繼承Serializable接口,由代碼中的輸出(注釋即為輸出內容)可得靜態變量SCHOOLNAME和SCHOOLID的值發生了變化,難道是靜態變量也被序列化了嗎?其實不是的,因為在當前的運行程序中,Student類的靜態變量的值已經發生了變化,如果真的已經序列化了,那么我們將序列化的那個函數去掉,讓程序從SourceFile/Student文件中反序列化,那么得到的SCHOOLNAME應該為HeBei University,SCHOOLID的值為2,然而當我們去掉序列化代碼,直接從文件反序列化,輸出SCHOOLNAME是Beijing University of Post and Telecommunications,SCHOOLID為1,說明static變量並沒有被序列化。
public class Student implements Serializable{ /** * */ private static final long serialVersionUID = 1L; public String name; public int id; public static String SCHOOLNAME = "Beijing University of Post and Telecommunications"; private static int SCHOOLID = 1; public Student(){ name = "zhangsan"; id = 2018111846; } public Student(String name, int id){ this.name = name; this.id = id; } public static void setSchool(String school_name, int school_id){ SCHOOLID = school_id; SCHOOLNAME = school_name; } public static int getSchoolID(){ return SCHOOLID; } } public class Test { /** * @param args */ public static void main(String[] args) { Student student = new Student(); student.setSchool("Hebei University",2); serialize(student); deserialize(); } /** * 序列化student對象 * @param student */ private static void serialize(Student student){ try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SourceFile/Student")); oos.writeObject(student); oos.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 從文件反序列化student對象 */ private static void deserialize(){ try { FileInputStream fis = new FileInputStream("SourceFile/Student"); Student student = (Student) new ObjectInputStream(fis).readObject(); System.out.println(student.name); //輸出zhangsan System.out.println(student.id); //輸出2018111846 System.out.println(student.SCHOOLNAME); //輸出HeBei University System.out.println(student.getSchoolID()); //輸出2 } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
-
-
外部序列化
-
Java語言還提供了另外一種方式來實現對象持久化,即外部序列化。外部序列化與序列化的主要區別在於序列化是內置的API,只需要實現Serializable接口,開發人員不需要編寫任何代碼就可以實現對象的序列化,而是用外部序列化時,Externalizable接口中的方法必須有開發人員實現。因此與實現Serializable接口的方法相比,使用Externalizable編寫程序的難度更大,但是由於控制權交給了開發者,在編程時有更多的靈活性,對需要持久化的那些屬性可以進行控制,可能提高程序的性能。
如下是外部序列化的代碼,當把序列化部分的代碼注釋到之后,發現薪金的輸出還是10000,則可以確定當執行外部序列化時,static靜態變量也被序列化了,而且方法中沒有序列化id屬性,則反序列化后發現id並沒有發生變化:
public class Teacher implements Externalizable{ private String name; private int id; private static int salary = 5000; public Teacher(){ name = "zhangsan"; id = 182566; } public static void setSalary(int s){ salary =s; } public int getID(){ return id; } public void setID(int id){ this.id = id; } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // TODO Auto-generated method stub Date date = (Date)in.readObject(); name = (String)in.readObject(); salary = (Integer)in.readObject(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println(sdf.format(date)); } @Override public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub Date date = new Date(); out.writeObject(date); out.writeObject(name); out.writeObject(salary); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); System.out.println(sdf.format(date)); } public String toString(){ return "教師名稱" + name + " 薪金" + salary; } } public class Test { /** * @param args */ public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.setSalary(10000); teacher.setID(111111); System.out.println(teacher.getID()); //輸出111111 serialize(teacher); Teacher teacher1 = deserialize(); System.out.println(teacher1.toString()); //輸出教師名稱zhangsan 薪金10000 System.out.println(teacher1.getID()); //輸出182566 } /** * 外部序列化teacher對象 * @param teacher */ private static void serialize(Teacher teacher){ try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SourceFile/Teacher")); oos.writeObject(teacher); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static Teacher deserialize(){ try { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SourceFile/Teacher")); return (Teacher)ois.readObject(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
Java反序列化
與序列化相對的是反序列化,它將流轉換為對象,在序列化與反序列化的過程中,serialVersionUID起着重要的作用,每一個類都有一個特定的serialVersionUID,在反序列化的過程中通過serialVersionUID判定類的兼容性,自定義serialVersionUID主要由如下3個優點。
- 提高程序運行效率。如果在類中未顯示聲明serialVersionUID,那么在序列化時會通過計算得到一個serialVersionUID。通過顯示聲明serialVersionUID的方式省去了計算的過程,提高了程序效率。
- 提高程序不同平台上的兼容性。由於各個平台計算serialVersionUID的方式可能不同,通過顯示的方式可以完全避免該問題。
- 增強程序各個版本的可兼容性。在默認情況下每個類都有唯一的一個serialVersionUID,因此當后期對類進行修改時,類的serialVersionUID值將會發生變化,這將會導致類在修改前對象的文件在修改后無法進行反序列化操作。同樣通過顯示聲明serialVersionUID也會解決該問題。
最后想說明,反序列化也是Java創建對象的一種方式,其他的還有new 類名()、通過clone()創建對象、通過反射機制創建對象。