java-序列化與反序列化

序列化和反序列化的概念

  1. 序列化:把java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化,這些字節(jié)序列可以被保存在磁盤上或通過網(wǎng)絡(luò)傳輸,以備以后重新恢復(fù)原來的對象
  2. 反序列化:把字節(jié)序列恢復(fù)為對象的過程稱為對象的反序列化。序列化機(jī)制使得對象可以脫離程序的運行而獨立存在

序列化的功能/用途

  1. 持久化對象:Java平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java對象,但一般情況下,只有當(dāng)JVM處于運行時,這些對象才可能存在,即,
    這些對象的生命周期不會比JVM的生命周期更長。但在現(xiàn)實應(yīng)用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象,并在將來重新讀取被保存的對象。把對象的字節(jié)序列永久地保存到硬盤上,通常存放在一個文件中,以此實現(xiàn)該功能 。java中的對象的內(nèi)部狀態(tài)只保存在內(nèi)存中,其生命周期最長與JVM的生命周期一樣,即JVM停止之后,所有對象都會被銷毀。
  2. 網(wǎng)絡(luò)傳輸:在網(wǎng)絡(luò)上傳送對象的字節(jié)序列。

實際應(yīng)用

  1. 在很多應(yīng)用中,需要對某些對象進(jìn)行序列化,讓它們離開內(nèi)存空間,入住物理硬盤,以便長期保存。比如最常見的是Web服務(wù)器中的Session對象,當(dāng)有 10萬用戶并發(fā)訪問,就有可能出現(xiàn)10萬個Session對象,內(nèi)存可能吃不消,于是Web容器就會把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對象還原到內(nèi)存中。
  2. 當(dāng)兩個進(jìn)程在進(jìn)行遠(yuǎn)程通信時,彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù),都會以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列,才能在網(wǎng)絡(luò)上傳送;接收方則需要把字節(jié)序列再恢復(fù)為Java對象。

實現(xiàn)

  1. java.io.Serializable接口,那么它就可以被序列化
  2. Externalizable:
    Serializable接口
    · 優(yōu)點:內(nèi)建支持
    · 優(yōu)點:易于實現(xiàn)
    · 缺點:占用空間過大
    · 缺點:由于額外的開銷導(dǎo)致速度變比較慢
    Externalizable接口
    · 優(yōu)點:開銷較少(程序員決定存儲什么)
    · 優(yōu)點:可能的速度提升
    · 缺點:虛擬機(jī)不提供任何幫助,也就是說所有的工作都落到了開發(fā)人員的肩上。
    在兩者之間如何選擇要根據(jù)應(yīng)用程序的需求來定。Serializable通常是最簡單的解決方案,但是它可能會導(dǎo)致出現(xiàn)不可接受的性能問題或空間問題;在出現(xiàn)這些問題的情況下,Externalizable可能是一條可行之路。

JDK類庫中的序列化API

  1. java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Object obj)方法可對參數(shù)指定的obj對象進(jìn)行序列化,把得到的字節(jié)序列寫到一個目標(biāo)輸出流中。
  2. java.io.ObjectInputStream代表對象輸入流,它的readObject()方法從一個源輸入流中讀取字節(jié)序列,再把它們反序列化為一個對象,并將其返回。

序列化與反序列化的編程實現(xiàn)

實現(xiàn)序列化接口的類

public class Person implements Serializable{
    private static final long serialVersionUID = -1015228989208411177L;
    private String name;   
        private  int age;  
    public Person(String name, int age) {    
        this.name = name;   
        this.age = age;   
    }   
    public String getName() {   
        return name;   
    }   
    public void setName(String name) {   
        this.name = name;   
    }      
    public int getAge() {   
        return age;   
    }      
    public void setAge(int age) {   
        this.age = age;   
    }   
}

序列化過程

public class WriteObject {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;   
        try {   
            //1.創(chuàng)建一個ObjectOutputStream   
            oos = new ObjectOutputStream(new FileOutputStream("/home/sunyan/object.txt"));   
            Person per = new Person("孫悟空", 500);  
            //2.將per對象寫入輸入流   
            oos.writeObject(per); 
        } catch (FileNotFoundException e) {   
             e.printStackTrace();   
        } catch (IOException e) {   
             e.printStackTrace();   
        }finally{   
            try {   
                if(oos != null){   
                    oos.close();   
                }   
            } catch (IOException e) {   
                 e.printStackTrace();   
            }   
        }   
    }
}

反序列化

public class ReadObject {
    public static void main(String[] args) {
        ObjectInputStream ois = null;              
        try {   
            //1.創(chuàng)建一個ObjectInputStream輸入流   
           ois = new ObjectInputStream(new FileInputStream("/home/sunyan/object.txt"));   
            //2.從輸入流中讀取一個Java對象,并將其強(qiáng)制類型轉(zhuǎn)換為Person對象   
            Person p = (Person) ois.readObject();
            System.out.println("名字為:" + p1.getName() + "\n年齡為:" + p1.getAge());
        } catch (FileNotFoundException e) {   
            e.printStackTrace();   
        } catch (IOException e) {   
            e.printStackTrace();   
        } catch (ClassNotFoundException e) {   
            e.printStackTrace();   
        }finally{   
            try {   
                if (ois == null) {   
                     ois.close();   
                }   
            } catch (IOException e) {   
                e.printStackTrace();   
            }   
        }   
    }
}

執(zhí)行結(jié)果:

  1. 如果我們向文件中使用序列化機(jī)制寫入了多個Java對象,使用反序列化機(jī)制恢復(fù)對象必須按照實際寫入的順序讀取。
    序列化
 Person per1 = new Person("孫悟空", 500);  
 Person per2 = new Person("孫小妹", 50); 
oos.writeObject(per1); 
 oos.writeObject(per2); 

反序列化

Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();   
System.out.println("名字為:" + p1.getName() + "\n年齡為:" + p1.getAge());
System.out.println("名字為:" + p2.getName() + "\n年齡為:" + p2.getAge()); 

執(zhí)行結(jié)果


  1. 對象引用的序列化
    如果類的屬性不是基本類型或者String類型,而是另一個引用類型,那么這個引用類型必須是可序列化的,否則該類也是不可序列化的,即使該類實現(xiàn)了Serializable,Externalizable接口。
public class Teacher implements Serializable{
    private String name;   
    //類的屬性是引用類型,也必須序列化。   
    //如果Person是不可序列化的,無論Teacher實現(xiàn)Serializable,Externalizable接口,則Teacher   
    //都是不可序列化的。   
    private Person student;   
    public Teacher(String name, Person student) {   
        super();   
        this.name = name;   
        this.student = student;   
    }   
    public String getName() {   
         return name;   
    }    
    public void setName(String name) {   
         this.name = name;   
    }      
    public Person getStudent() {   
         return student;   
    }    
    public void setStudent(Person student) {   
        this.student = student;   
    }   
}

上述代碼中,Teacher中有一個引用類型student,若Person未實現(xiàn)接口Serializable。即

public class Person implements Serializable{
}

則在序列化過程中,會報錯


  1. Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對象型的是 null。
    在Person中更改如下代碼
private  transient int age; 

此時1中的代碼,經(jīng)序列化和反序列化后,執(zhí)行結(jié)果如下


  1. s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D
    s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D?:? ?字?面?意?思?上?是?序?列?化?的?版?本?號?,凡是實現(xiàn)Serializable接口的類都有一個表示序列化版本標(biāo)識符的靜態(tài)變量。序列化 ID 是否一致,決定 虛擬機(jī)是否允許反序列化。
    實現(xiàn)Serializable接口的類如果類中沒有添加serialVersionUID,那么就會出現(xiàn)如下的警告提示



    serialVersionUID有兩種生成方式:
    采用 Add default serial version ID
    這種方式生成的serialVersionUID是1L,例如:

 private static final long serialVersionUID = 1L;

采用 Add generated serial version ID這種方式生成的serialVersionUID是根據(jù)類名,接口名,方法和屬性等來生成的,例如:

private static final long serialVersionUID = -1015228989208411177L;

對1中的代碼序列化后,修改

private static final long serialVersionUID = -1015228989208411177L;

private static final long serialVersionUID = -1015228989208411178L;

此時,進(jìn)行反序列化,會報錯


反序列化漏洞危害

當(dāng)應(yīng)用代碼從用戶接受序列化數(shù)據(jù),并試圖反序列化改數(shù)據(jù)進(jìn)行下一步處理時,會產(chǎn)生反序列化漏洞,其中最有危害性的就是遠(yuǎn)程代碼注入。
這種漏洞產(chǎn)生原因是,java類ObjectInputStream在執(zhí)行反序列化時,并不會對自身的輸入進(jìn)行檢查,這就說明惡意攻擊者可能也可以構(gòu)建特定的輸入,在 ObjectInputStream類反序列化之后會產(chǎn)生非正常結(jié)果,利用這一方法就可以實現(xiàn)遠(yuǎn)程執(zhí)行任意代碼。

最后再加一些相關(guān)知識點

1、聲明為static和transient的成員數(shù)據(jù)不能被串行化,因為static代表類的狀態(tài),transient代表對象的臨時數(shù)據(jù)。

2、要想將父類對象也序列化,就需要讓父類也實現(xiàn)Serializable 接口。
3、服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù),對象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時,才可以對密碼進(jìn)行讀取,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,495評論 0 10
  • 原帖地址:原帖個人網(wǎng)站地址:個人網(wǎng)站簡書對markdown的支持太完美了,我竟然可以直接Ctrl C/V過來。 定...
    ryderchan閱讀 3,951評論 1 9
  • 簡介 對于一個存在于Java虛擬機(jī)中的對象來說,其內(nèi)部的狀態(tài)只保持在內(nèi)存中。JVM停止之后,這些狀態(tài)就丟失了。在很...
    FX_SKY閱讀 867評論 0 0
  • 問題 Java序列化與反序列化是什么?為什么需要序列化與反序列化?有什么好處?如何實現(xiàn)Java序列化與反序列化? ...
    海邊的卡夫卡丶閱讀 428評論 1 1
  • 序列化的意義 1.永久存儲某個jvm中運行時的對象。2.對象可以網(wǎng)絡(luò)傳輸3.rmi調(diào)用都是以序列化的方式傳輸參數(shù) ...
    炫邁哥閱讀 734評論 0 0

友情鏈接更多精彩內(nèi)容