溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java中序列化與反序列化的概念和使用方法介紹

發(fā)布時間:2021-07-29 16:36:16 來源:億速云 閱讀:151 作者:chen 欄目:開發(fā)技術

這篇文章主要介紹“java中序列化與反序列化的概念和使用方法介紹”,在日常操作中,相信很多人在java中序列化與反序列化的概念和使用方法介紹問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”java中序列化與反序列化的概念和使用方法介紹”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

一、概念

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

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

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

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

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

三、涉及到的javaAPI 

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

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

         只有實現(xiàn)了Serializable或Externalizable接口的類的對象才能被序列化,否則拋出異常。

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

         序列化:

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

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

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

                          out.writeObject("Hello");

                          out.writeObject(new Date());

         反序列化:       

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

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

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

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

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

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

基本實現(xiàn)代碼

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的作用

        在以上的介紹中,我們在代碼里會發(fā)現(xiàn)有這樣一個變量: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會把傳來的字節(jié)流中的serialVersionUID與本地實體類中的serialVersionUID進行比較,如果相同則認為是一致的,便可以進行反序列化,否則就會報序列化版本不一致的異常。等會我們可以通過代碼驗證一下。

       序列化ID如何產生:

       當我們一個實體類中沒有顯示的定義一個名為“serialVersionUID”、類型為long的變量時,Java序列化機制會根據(jù)編譯時的class自動生成一個serialVersionUID作為序列化版本比較,這種情況下,只有同一次編譯生成的class才會生成相同的serialVersionUID。譬如,當我們編寫一個類時,隨著時間的推移,我們因為需求改動,需要在本地類中添加其他的字段,這個時候再反序列化時便會出現(xiàn)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);在讀取數(shù)據(jù)的時候第一次會正常讀取,不會報錯,當讀取第二次的時候,就會報出java.io.StreamCorruptedException: invalid type code: AC的錯誤。

問題分析:

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

解決方案:

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

一、運用集合:

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

二、重寫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循環(huán)中,每次都創(chuàng)建一個新的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方法就會很麻煩。這時我們可以用序列化的方式來實現(xiàn)對象的深克隆。

所有的引用類型都要實現(xiàn)序列化

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID
  public Inner inner;
 //Discription:[深度復制方法,需要對象及對象所有的對象屬性都實現(xiàn)序列化] 
  public Outer myclone() {
      Outer outer = null;
      try { // 將該對象序列化成流,因為寫在流里的是對象的一個拷貝,而原對象仍然存在于JVM里面。所以利用這個特性可以實現(xiàn)對象的深拷貝
          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中序列化之子類繼承父類序列化

父類實現(xiàn)了Serializable,子類不需要實現(xiàn)Serializable

  相關注意事項

    a)序列化時,只對對象的狀態(tài)進行保存,而不管對象的方法;

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

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

    d)并非所有的對象都可以序列化,至于為什么不可以,有很多原因了,比如:

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

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

  例子:

 1,父類實現(xiàn)了Serializable,子類沒有,

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

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

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

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

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

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

當一個父類實現(xiàn)Serializable接口后,他的子類都將自動的實現(xiàn)序列化。

以下驗證了這一點:

package Serial;
import java.io.Serializable; 
public class SuperC implements Serializable {//父類實現(xiàn)了序列化 
 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

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

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

  要為一個沒有實現(xiàn)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. ”

也就是說,要為一個沒有實現(xiàn)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中所說的一樣,父類缺少無參構造函數(shù)是不行的。

  接下來,按照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,它使用默認的序列化行為,然后序列化父類的域;反序列化的時候也一樣。

  歸納一下:

  目的 行為

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

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

到此,關于“java中序列化與反序列化的概念和使用方法介紹”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI