欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2021年07月29日 11:46:59   作者:Rooker  
序列化是一種對(duì)象持久化的手段,普遍應(yīng)用在網(wǎng)絡(luò)傳輸、RMI等場(chǎng)景中,這篇文章主要給大家總結(jié)介紹了關(guān)于java序列化與反序列化的使用方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

一、概念

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

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

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

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

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

三、涉及到的javaAPI 

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

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

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

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

         序列化:

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

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

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

                          out.writeObject("Hello");

                          out.writeObject(new Date());

         反序列化:       

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

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

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

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

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

        說明:為了正確讀取數(shù)據(jù),完成反序列化,必須保證向?qū)ο筝敵隽鲗憣?duì)象的順序與從對(duì)象輸入流中讀對(duì)象的順序一致。

基本實(shí)現(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();
        }

    }
}

知識(shí)點(diǎn)總結(jié)

1.java 序列化ID的作用

        在以上的介紹中,我們?cè)诖a里會(huì)發(fā)現(xiàn)有這樣一個(gè)變量:serialVersionUID,那么這個(gè)變量serialVersionUID到底具有什么作用呢?能不能去掉呢?

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

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

序列化ID的作用: 

       其實(shí),這個(gè)序列化ID起著關(guān)鍵的作用,它決定著是否能夠成功反序列化!簡(jiǎn)單來說,java的序列化機(jī)制是通過在運(yùn)行時(shí)判斷類的serialVersionUID來驗(yàn)證版本一致性的。在進(jìn)行反序列化時(shí),JVM會(huì)把傳來的字節(jié)流中的serialVersionUID與本地實(shí)體類中的serialVersionUID進(jìn)行比較,如果相同則認(rèn)為是一致的,便可以進(jìn)行反序列化,否則就會(huì)報(bào)序列化版本不一致的異常。等會(huì)我們可以通過代碼驗(yàn)證一下。

       序列化ID如何產(chǎn)生:

       當(dāng)我們一個(gè)實(shí)體類中沒有顯示的定義一個(gè)名為“serialVersionUID”、類型為long的變量時(shí),Java序列化機(jī)制會(huì)根據(jù)編譯時(shí)的class自動(dòng)生成一個(gè)serialVersionUID作為序列化版本比較,這種情況下,只有同一次編譯生成的class才會(huì)生成相同的serialVersionUID。譬如,當(dāng)我們編寫一個(gè)類時(shí),隨著時(shí)間的推移,我們因?yàn)樾枨蟾膭?dòng),需要在本地類中添加其他的字段,這個(gè)時(shí)候再反序列化時(shí)便會(huì)出現(xiàn)serialVersionUID不一致,導(dǎo)致反序列化失敗。那么如何解決呢?便是在本地類中添加一個(gè)“serialVersionUID”變量,值保持不變,便可以進(jìn)行序列化和反序列化。

總結(jié):

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

2.如何使某些屬性不被序列化進(jìn)去?

使用 transient 關(guān)鍵字

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.如何判斷是否還有可讀對(duì)象

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問題解決

問題描述:

每次向一個(gè)文件中序列化對(duì)象時(shí) ,每次只想向文件末尾追加對(duì)象,而不是覆蓋,可以使用FileInputStream(文件名,true);在讀取數(shù)據(jù)的時(shí)候第一次會(huì)正常讀取,不會(huì)報(bào)錯(cuò),當(dāng)讀取第二次的時(shí)候,就會(huì)報(bào)出java.io.StreamCorruptedException: invalid type code: AC的錯(cuò)誤。

問題分析:

由于用FileInputStream(文件名,true)向同一個(gè)文件中序列化對(duì)象,每次都會(huì)向文件中序列化一個(gè)header。在反序列化的時(shí)候每個(gè) ObjectInputStream 對(duì)象只會(huì)讀取一個(gè)header,那么當(dāng)遇到第二個(gè)的時(shí)候就會(huì)報(bào)錯(cuò),導(dǎo)致出現(xiàn)異常。

解決方案:

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

一、運(yùn)用集合:

在第一次序列化對(duì)象之前,把要序列化的對(duì)象添加到集合中,把這個(gè)集合序列化到文件中。然后每次序列化之前,除了把準(zhǔn)備序列化的對(duì)象添加到集合中,再把已經(jīng)序列化的集合反序列化回來添加到集合中,然后再把集合序列化到文件中。

二、重寫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)建一個(gè)新的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一個(gè)新的ObjectInputStream
            ois = new ObjectInputStream(fis);
            Person p = (Person) ois.readObject();
            list.add(p);
        }
        ois.close();
        return list;
    }
}

5.序列化與克隆

解決多層克隆問題

如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來實(shí)現(xiàn)對(duì)象的深克隆。

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

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID
  public Inner inner;
 //Discription:[深度復(fù)制方法,需要對(duì)象及對(duì)象所有的對(duì)象屬性都實(shí)現(xiàn)序列化] 
  public Outer myclone() {
      Outer outer = null;
      try { // 將該對(duì)象序列化成流,因?yàn)閷懺诹骼锏氖菍?duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于JVM里面。所以利用這個(gè)特性可以實(shí)現(xiàn)對(duì)象的深拷貝
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
      // 將流序列化成對(duì)象
          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中序列化之子類繼承父類序列化

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

  相關(guān)注意事項(xiàng)

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

    b)當(dāng)一個(gè)父類實(shí)現(xiàn)序列化,子類自動(dòng)實(shí)現(xiàn)序列化,不需要顯式實(shí)現(xiàn)Serializable接口;

    c)當(dāng)一個(gè)對(duì)象的實(shí)例變量引用其他對(duì)象,序列化該對(duì)象時(shí)也把引用對(duì)象進(jìn)行序列化;

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

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

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

  例子:

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

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

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

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

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

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

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

當(dāng)一個(gè)父類實(shí)現(xiàn)Serializable接口后,他的子類都將自動(dòng)的實(shí)現(xiàn)序列化。

以下驗(yàn)證了這一點(diǎn):

package Serial;
import java.io.Serializable; 
public class SuperC implements Serializable {//父類實(shí)現(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{ 
  …此處省略 
 } 
} 
}

運(yùn)行結(jié)果如下:

supervalue: 100 sub: 200

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

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

  要為一個(gè)沒有實(shí)現(xiàn)Serializable接口的父類,編寫一個(gè)能夠序列化的子類是一件很麻煩的事情。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. ”

也就是說,要為一個(gè)沒有實(shí)現(xiàn)Serializable接口的父類,編寫一個(gè)能夠序列化的子類要做兩件事情:

  其一、父類要有一個(gè)無參的constructor;

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

  我們將SuperC的Serializable接口去掉,而給SubC加上Serializable接口。運(yùn)行后產(chǎn)生錯(cuò)誤:

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中所說的一樣,父類缺少無參構(gòu)造函數(shù)是不行的。

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

public abstract class SuperC { 
 int supervalue; 
 public SuperC(int supervalue) { 
  this.supervalue = supervalue; 
 }
 public SuperC(){}//增加一個(gè)無參的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();//先序列化對(duì)象 
   out.writeInt(supervalue);//再序列化父類的域 
  } 
  private void readObject(java.io.ObjectInputStream in) 
  throws IOException, ClassNotFoundException{ 
   in.defaultReadObject();//先反序列化對(duì)象 
   supervalue=in.readInt();//再反序列化父類的域 
  } 
}

運(yùn)行結(jié)果證明了這種方法是正確的。在此處我們用到了writeObject/ readObject方法,這對(duì)方法如果存在的話,序列化時(shí)就會(huì)被調(diào)用,以代替默認(rèn)的行為(以后還要探討,先了解這么多)。我們?cè)谛蛄谢瘯r(shí),首先調(diào)用了ObjectOutputStream的defaultWriteObject,它使用默認(rèn)的序列化行為,然后序列化父類的域;反序列化的時(shí)候也一樣。

  歸納一下:

  目的 行為

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

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

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

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

總結(jié)

到此這篇關(guān)于java序列化與反序列化使用方法的文章就介紹到這了,更多相關(guān)java序列化與反序列化使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論