java序列化與反序列化的使用方法匯總

一、概念

       java對象序列化的意思就是將對象的狀態轉化成字節流,以後可以通過這些值再生成相同狀態的對象。對象序列化是對象持久化的一種實現方法,它是將對象的屬性和方法轉化為一種序列化的形式用於存儲和傳輸。反序列化就是根據這些保存的信息重建對象的過程。

       序列化:將java對象轉化為字節序列的過程。

       反序列化:將字節序列轉化為java對象的過程。

二、為什麼要序列化和反序列化

       我們知道,當兩個進程進行遠程通信時,可以相互發送各種類型的數據,包括文本、圖片、音頻、視頻等, 而這些數據都會以二進制序列的形式在網絡上傳送。那麼當兩個Java進程進行通信時,能否實現進程間的對象傳送呢?答案是可以的。如何做到呢?這就需要Java序列化與反序列化瞭。換句話說,一方面,發送方需要把這個Java對象轉換為字節序列,然後在網絡上傳送;另一方面,接收方需要從字節序列中恢復出Java對象。當我們明晰瞭為什麼需要Java序列化和反序列化後,我們很自然地會想Java序列化的好處。其好處一是實現瞭數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件裡),二是,利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。

三、涉及到的javaAPI 

          java.io.ObjectOutputStream表示對象輸出流,它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。

          java.io.ObjectInputStream表示對象輸入流,它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成為一個對象,並將其返回。

         隻有實現瞭Serializable或Externalizable接口的類的對象才能被序列化,否則拋出異常。

四、序列化和反序列化的步驟

         序列化:

           步驟一:創建一個對象輸出流,它可以包裝一個其它類型的目標輸出流,如文件輸出流:

                          ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“目標地址路徑”));

         步驟二:通過對象輸出流的writeObject()方法寫對象:

                          out.writeObject(“Hello”);

                          out.writeObject(new Date());

         反序列化:       

          步驟一:創建一個對象輸入流,它可以包裝一個其它類型輸入流,如文件輸入流:

                          ObjectInputStream in = new ObjectInputStream(new fileInputStream(“目標地址路徑”));

         步驟二:通過對象輸出流的readObject()方法讀取對象:

                        String obj1 = (String)in.readObject();

                        Date obj2 =  (Date)in.readObject();

        說明:為瞭正確讀取數據,完成反序列化,必須保證向對象輸出流寫對象的順序與從對象輸入流中讀對象的順序一致。

基本實現代碼

package com.my.test.clone;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TestStudent {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        Student stu1 = new Student("a", "a1");
        stu1.setAddress(new Address("ZZ"));

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        System.out.println(stu1);
        try {
            oos = new ObjectOutputStream(new FileOutputStream("D:/test/stu.txt"));
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            
            ois = new ObjectInputStream(new FileInputStream("D:/test/stu.txt"));
            Student stu2 = (Student) ois.readObject();
            Student stu3 = (Student) ois.readObject();
            //Student stu4 = (Student) ois.readObject();
            System.out.println(stu2);
            System.out.println(stu3);
            //System.out.println(stu4);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            if (oos != null) {
                oos.close();
            }
            if(ois!= null){
                ois.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

知識點總結

1.java 序列化ID的作用

        在以上的介紹中,我們在代碼裡會發現有這樣一個變量:serialVersionUID,那麼這個變量serialVersionUID到底具有什麼作用呢?能不能去掉呢?

public class Student implements Serializable {
    
    
    /**
     * 
     */
    private static final long serialVersionUID = 6866904399011716299L;

    private String stuId;
    
    private transient String stuName;
    
    private Address address;
。。。。。。
}

序列化ID的作用: 

       其實,這個序列化ID起著關鍵的作用,它決定著是否能夠成功反序列化!簡單來說,java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地實體類中的serialVersionUID進行比較,如果相同則認為是一致的,便可以進行反序列化,否則就會報序列化版本不一致的異常。等會我們可以通過代碼驗證一下。

       序列化ID如何產生:

       當我們一個實體類中沒有顯示的定義一個名為“serialVersionUID”、類型為long的變量時,Java序列化機制會根據編譯時的class自動生成一個serialVersionUID作為序列化版本比較,這種情況下,隻有同一次編譯生成的class才會生成相同的serialVersionUID。譬如,當我們編寫一個類時,隨著時間的推移,我們因為需求改動,需要在本地類中添加其他的字段,這個時候再反序列化時便會出現serialVersionUID不一致,導致反序列化失敗。那麼如何解決呢?便是在本地類中添加一個“serialVersionUID”變量,值保持不變,便可以進行序列化和反序列化。

總結:

       虛擬機是否允許反序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。

2.如何使某些屬性不被序列化進去?

使用 transient 關鍵字

ublic class Student implements Serializable {
    
    
    /**
     * 
     */
    private static final long serialVersionUID = 6866904399011716299L;

    private String stuId;
    
    private transient String stuName;
    
    private Address address;
    
。。。。。。
}

Student [stuId=a, stuName=a1, address=Address [addr=ZZ]]
Student [stuId=a, stuName=null, address=Address [addr=ZZ]]

3.如何判斷是否還有可讀對象

oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            FileInputStream fis = new FileInputStream(file);
            ois = new ObjectInputStream(fis);
            while (fis.available()>0) {                
                System.out.println(ois.readObject());
            }

4.覆蓋與不覆蓋

oos = new ObjectOutputStream(new FileOutputStream("D:/test/stu.txt", true));

java.io.StreamCorruptedException: invalid type code: AC問題解決

問題描述:

每次向一個文件中序列化對象時 ,每次隻想向文件末尾追加對象,而不是覆蓋,可以使用FileInputStream(文件名,true);在讀取數據的時候第一次會正常讀取,不會報錯,當讀取第二次的時候,就會報出java.io.StreamCorruptedException: invalid type code: AC的錯誤。

問題分析:

由於用FileInputStream(文件名,true)向同一個文件中序列化對象,每次都會向文件中序列化一個header。在反序列化的時候每個 ObjectInputStream 對象隻會讀取一個header,那麼當遇到第二個的時候就會報錯,導致出現異常。

解決方案:

一共三種方法,推薦使用第二種;

一、運用集合:

在第一次序列化對象之前,把要序列化的對象添加到集合中,把這個集合序列化到文件中。然後每次序列化之前,除瞭把準備序列化的對象添加到集合中,再把已經序列化的集合反序列化回來添加到集合中,然後再把集合序列化到文件中。

二、重寫ObjectOutputSream的writeStreamHeader()方法:

判斷是不是第一次寫入,若是則寫入頭部,若不是則不寫入頭部

/**
重寫writeStreamHeader()方法
*/
class MyObjectOutputStream  extends ObjectOutputStream{

    public MyObjectOutputStream(OutputStream out) throws IOException {
        super(out);
    }

    public void writeStreamHeader() throws IOException{
        return;
    }
//序列化
    public static void set(File file,Person p) throws Exception{
        FileOutputStream fos = new FileOutputStream(file,true);
        /**
        判斷是否是第一次寫入
        */
        if(file.length()<1){
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(p);
            oos.close();
        }else{
            MyObjectOutputStream mos = new MyObjectOutputStream(fos);
            mos.writeObject(p);
            mos.close();
        }
    }

    //反序列化
    public static List<Person> get(File file) throws Exception{
        List<Person> list = new ArrayList<Person>();
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        while(fis.available()>0){
            Person p = (Person) ois.readObject();
            list.add(p);
        }
        ois.close();
        return list;
    }
}

三:不重寫ObjectOutputSream的writeStreamHeader()方法。在反序列化的while循環中,每次都創建一個新的ObjectInputStream用來讀取header

public class SerializableDemo03{
    public static void main(String[] args) throws Exception {
        File file = new File(".\\c.txt");
        Person p = new Person("lisi",19);
        set(file,p);
        List<Person> list = get(file);
        for(Person per:list){
            System.out.println(per);
        }
    }

    public static void set(File file,Person p) throws Exception{
        FileOutputStream fos = new FileOutputStream(file,true);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(p);
        oos.close();
    }

    public static List<Person> get(File file) throws Exception{
        List<Person> list = new ArrayList<Person>();
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = null;
        while(fis.available()>0){
            //每次都new一個新的ObjectInputStream
            ois = new ObjectInputStream(fis);
            Person p = (Person) ois.readObject();
            list.add(p);
        }
        ois.close();
        return list;
    }
}

5.序列化與克隆

解決多層克隆問題

如果引用類型裡面還包含很多引用類型,或者內層引用類型的類裡面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深克隆。

所有的引用類型都要實現序列化

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID
  public Inner inner;
 //Discription:[深度復制方法,需要對象及對象所有的對象屬性都實現序列化] 
  public Outer myclone() {
      Outer outer = null;
      try { // 將該對象序列化成流,因為寫在流裡的是對象的一個拷貝,而原對象仍然存在於JVM裡面。所以利用這個特性可以實現對象的深拷貝
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
      // 將流序列化成對象
          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais);
          outer = (Outer) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return outer;
  }
}

 6.java中序列化之子類繼承父類序列化

父類實現瞭Serializable,子類不需要實現Serializable

  相關註意事項

    a)序列化時,隻對對象的狀態進行保存,而不管對象的方法;

    b)當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口;

    c)當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化;

    d)並非所有的對象都可以序列化,至於為什麼不可以,有很多原因瞭,比如:

        1.安全方面的原因,比如一個對象擁有private,public等field,對於一個要傳輸的對象,比如寫到文件,或者進行rmi傳輸等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的。transient標記的屬性,也不會被實例化

       2. 資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分配,而且,也是沒有必要這樣實現。

  例子:

 1,父類實現瞭Serializable,子類沒有,

父類有int a = 1;int b = 2;int c = 3

子類有int d = 4;int e = 5

序列化子類的時候,d和e會不會被序列化?(答案:會)

2,反過來父類未實現Serializable,子類實現瞭,序列化子類實例的時候,父類的屬性是直接被跳過不保存,還是能保存但不能還原?(答案:值不保存)

解:父類實現接口後,所有派生類的屬性都會被序列化。子類實現接口的話,父類的屬性值丟失。

java中序列化之子類繼承父類序列化

當一個父類實現Serializable接口後,他的子類都將自動的實現序列化。

以下驗證瞭這一點:

package Serial;
import java.io.Serializable; 
public class SuperC implements Serializable {//父類實現瞭序列化 
 int supervalue; 
 public SuperC(int supervalue) { 
  this.supervalue = supervalue; 
 } 
 public String toString() { 
  return "supervalue: "+supervalue; 
 } 
} 

public class SubC extends SuperC {//子類 
 int subvalue; 

 public SubC(int supervalue,int subvalue) { 
  super(supervalue); 
  this.subvalue=subvalue; 
 } 

 public String toString() { 
  return super.toString()+" sub: "+subvalue; 
 } 
} 

public class Test1 { 

 public static void main(String [] args){ 
  SubC subc=new SubC(100,200); 
  FileInputStream in=null; 
  FileOutputStream out=null; 
  ObjectInputStream oin=null; 
  ObjectOutputStream oout=null; 
  try { 
   out = new FileOutputStream("Test1.txt");//子類序列化 
   oout = new ObjectOutputStream(out); 
   oout.writeObject(subc); 
   oout.close(); 
   oout=null; 

   in = new FileInputStream("Test1.txt"); 
   oin = new ObjectInputStream(in); 
   SubC subc2=(SubC)oin.readObject();//子類反序列化 
   System.out.println(subc2); 
  } catch (Exception ex){ 
   ex.printStackTrace(); 
  } finally{ 
  …此處省略 
 } 
} 
}

運行結果如下:

supervalue: 100 sub: 200

  可見子類成功的序列化/反序列化瞭。

  怎管讓子類實現序列化看起來是一件很簡單的事情,但有的時候,往往我們不能夠讓父類實現Serializable接口,原因是有時候父類是抽象的(這並沒有關系),並且父類不能夠強制每個子類都擁有序列化的能力。換句話說父類設計的目的僅僅是為瞭被繼承。

  要為一個沒有實現Serializable接口的父類,編寫一個能夠序列化的子類是一件很麻煩的事情。java docs中提到:

“To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring
 the state of the supertype’s public, protected, and (if accessible) package fields. The subtype may assume this responsibility
 only if the class it extends has an accessible no-arg constructor to initialize the class’s state. It is an error to declare a
class Serializable if this is not the case. The error will be detected at runtime. ”

也就是說,要為一個沒有實現Serializable接口的父類,編寫一個能夠序列化的子類要做兩件事情:

  其一、父類要有一個無參的constructor;

  其二、子類要負責序列化(反序列化)父類的域。

  我們將SuperC的Serializable接口去掉,而給SubC加上Serializable接口。運行後產生錯誤:

java.lang.Error: Unresolved compilation problem:
Serializable cannot be resolved or is not a valid superinterface
at Serial.SubC.<init>(SubC.java:15)
at Serial.Test1.main(Test1.java:19)
Exception in thread “main”

  果真如docs中所說的一樣,父類缺少無參構造函數是不行的。

  接下來,按照docs中的建議我們改寫這個例子:

public abstract class SuperC { 
 int supervalue; 
 public SuperC(int supervalue) { 
  this.supervalue = supervalue; 
 }
 public SuperC(){}//增加一個無參的constructor 
  public String toString() { 
   return "supervalue: "+supervalue; 
  } 
 } 

 public class SubC extends SuperC implements Serializable { 
  int subvalue; 

  public SubC(int supervalue,int subvalue) { 
   super(supervalue); 
   this.subvalue=subvalue; 
  } 

  public String toString() { 
   return super.toString()+" sub: "+subvalue; 
  } 

  private void writeObject(java.io.ObjectOutputStream out) 
  throws IOException{ 
   out.defaultWriteObject();//先序列化對象 
   out.writeInt(supervalue);//再序列化父類的域 
  } 
  private void readObject(java.io.ObjectInputStream in) 
  throws IOException, ClassNotFoundException{ 
   in.defaultReadObject();//先反序列化對象 
   supervalue=in.readInt();//再反序列化父類的域 
  } 
}

運行結果證明瞭這種方法是正確的。在此處我們用到瞭writeObject/ readObject方法,這對方法如果存在的話,序列化時就會被調用,以代替默認的行為(以後還要探討,先瞭解這麼多)。我們在序列化時,首先調用瞭ObjectOutputStream的defaultWriteObject,它使用默認的序列化行為,然後序列化父類的域;反序列化的時候也一樣。

  歸納一下:

  目的 行為

  為一個實現Serializable接口的父類,編寫一個能夠序列化的子類 子類將自動的實現序列化

  為一個沒有實現Serializable接口的父類,編寫一個能夠序列化的子類 1, 父類要有一個無參的constructor;2, 子類要先序列化自身,然後子類要負責序列化父類的域

引用:http://www.yesky.com/376/1908876.shtml

跟多參考:http://www.ibm.com/developerworks/cn/java/j-lo-serial/

總結

到此這篇關於java序列化與反序列化使用方法的文章就介紹到這瞭,更多相關java序列化與反序列化使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: