国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

Java對(duì)象序列化與反序列化

2019-11-14 22:16:49
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
java對(duì)象序列化與反序列化

對(duì)象序列化的目標(biāo)是將對(duì)象保存在磁盤(pán)中或者在網(wǎng)絡(luò)中進(jìn)行傳輸。實(shí)現(xiàn)的機(jī)制是允許將對(duì)象轉(zhuǎn)為與平臺(tái)無(wú)關(guān)的二進(jìn)制流。

java中對(duì)象的序列化機(jī)制是將允許對(duì)象轉(zhuǎn)為字節(jié)序列。這些字節(jié)序列可以使Java對(duì)象脫離程序存在,從而可以保存在磁盤(pán)上,也可以在網(wǎng)絡(luò)間傳輸。

對(duì)象的序列化是將一個(gè)Java對(duì)象寫(xiě)入IO流;與此對(duì)應(yīng)的,反序列化則是從IO流中恢復(fù)一個(gè)Java對(duì)象。

實(shí)現(xiàn)序列化

如果要將一個(gè)java對(duì)象序列化,那么對(duì)象的類(lèi)需要是可序列化的。要讓類(lèi)可序列化,那么這個(gè)類(lèi)需要實(shí)現(xiàn)如下兩個(gè)接口:

  • Serializable
  • Externalizable
使用Serializable序列化

實(shí)現(xiàn)Serializable接口非常簡(jiǎn)單,只要讓java實(shí)現(xiàn)Serializable接口即可,無(wú)需實(shí)現(xiàn)任何方法。

一個(gè)類(lèi)一旦實(shí)現(xiàn)了Serializable接口,那么該類(lèi)的對(duì)象就是可序列化的。實(shí)現(xiàn)類(lèi)的對(duì)象的序列化可以使用ObjectOutputStream,實(shí)現(xiàn)步驟如下:

  • 創(chuàng)建ObjectOutputStream對(duì)象;
  • 調(diào)用ObjectOutputStream的writeObject方法輸出對(duì)象。

以下是一個(gè)實(shí)例:

package com.zhyea.test;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) *  * @author robin * @date 2014年12月18日 */public class SerialTest {    public static void main(String[] args) {        ObjectOutputStream oos = null;        try {            oos = new ObjectOutputStream(new FileOutputStream("D://object.txt"));            Person robin = new Person("robin", 29);            oos.writeObject(robin);        } catch (IOException e) {            e.PRintStackTrace();        } finally {            if (null != oos) {                try {                    oos.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }}/** * 序列化測(cè)試用對(duì)象 *  * @author robin * @date 2014年12月18日 */class Person implements Serializable{        private static final long serialVersionUID = -6412852654889352693L;        /**     * 姓名     */    private String name;    /**     * 年齡     */    private int age;    public Person() {    }    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;    }}

如上的代碼實(shí)現(xiàn)了將一個(gè)Person對(duì)象保存在了磁盤(pán)的一個(gè)文本文件object.txt上。運(yùn)行程序在D盤(pán)上生成了一個(gè)object.txt文件。以下是文件內(nèi)容:

image

有亂碼(字節(jié)流轉(zhuǎn)字符流導(dǎo)致的),但仍不影響我們分辨出里面是不是我們保存的對(duì)象。

接下來(lái)需要反序列化將Person對(duì)象從磁盤(pán)上讀出。相應(yīng)的反序列化需要使用的類(lèi)是ObjectInputStream,反序列化步驟如下:

  • 創(chuàng)建ObjectInputStream對(duì)象;
  • 使用ObjectInputStream的readObject方法取出對(duì)象。

接下來(lái),重構(gòu)下我們的代碼,實(shí)現(xiàn)反序列化,如下:

package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) *  * @author robin * @date 2014年12月18日 */public class SerialTest {    public static void main(String[] args) {        Person robin = new Person("robin", 29);        String savePath = "D://object.txt";        SerialTest test = new SerialTest();        try {            test.serialize(robin, savePath);            Person person = (Person) test.deSerialize(savePath);            System.out.println("Name:" + person.getName() + "   Age:"                    + person.getAge());        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }    /**     * 實(shí)現(xiàn)序列化     *      * @param obj     *            要被序列化保存的對(duì)象     * @param path     *            保存地址     * @throws IOException     */    public void serialize(Object obj, String path) throws IOException {        ObjectOutputStream oos = null;        try {            oos = new ObjectOutputStream(new FileOutputStream(path));            oos.writeObject(obj);        } finally {            if (null != oos)                oos.close();        }    }    /**     * 反序列化取出對(duì)象     *      * @param path     *            被序列化對(duì)象保存的位置     * @return     * @throws IOException     * @throws ClassNotFoundException     */    public Object deSerialize(String path) throws IOException,            ClassNotFoundException {        ObjectInputStream ois = null;        try {            ois = new ObjectInputStream(new FileInputStream(path));            return ois.readObject();        } finally {            if (null != ois)                ois.close();        }    }}/** * 序列化測(cè)試用對(duì)象 *  * @author robin * @date 2014年12月18日 */class Person implements Serializable {    private static final long serialVersionUID = -6412852654889352693L;    /**     * 姓名     */    private String name;    /**     * 年齡     */    private int age;    public Person() {    }    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;    }}

關(guān)于對(duì)象序列化與反序列化還有幾點(diǎn)需要注意:

  • 反序列化無(wú)需通過(guò)構(gòu)造器初始化對(duì)象;
  • 如果使用序列化機(jī)制向文件中寫(xiě)入了多個(gè)對(duì)象,那么取出和寫(xiě)入的順序必須一致;
  • Java對(duì)類(lèi)的對(duì)象進(jìn)行序列化時(shí),若類(lèi)中存在對(duì)象引用(且值不為null),也會(huì)對(duì)類(lèi)的引用對(duì)象進(jìn)行序列化。
使用transient

在一些特殊場(chǎng)景下,比如銀行賬戶(hù)對(duì)象,出于保密考慮,不希望對(duì)存款金額進(jìn)行序列化。或者類(lèi)的一些引用類(lèi)型的成員是不可序列化的。此時(shí)可以使用transient關(guān)鍵字修飾不想被或者不能被序列化的成員變量。

繼續(xù)調(diào)整我們的代碼來(lái)做演示:

package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) *  * @author robin * @date 2014年12月18日 */public class SerialTest {    public static void main(String[] args) {        Person robin = new Person("robin", 29);        School school = new School("XX學(xué)校");                Teacher tRobin = new Teacher(robin);        tRobin.setSchool(school);        tRobin.setSalary(12.0);                String savePath = "D://object.txt";        SerialTest test = new SerialTest();        try {            test.serialize(savePath, tRobin);                        Teacher t = (Teacher) test.deSerialize(savePath);            System.out.println("Name:" + t.getPerson().getName()                             +" Salary:" + t.getSalary());        } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }    /**     * 實(shí)現(xiàn)序列化     *      * @param obj     *            要被序列化保存的對(duì)象     * @param path     *            保存地址     * @throws IOException     */    public void serialize(String path, Object ... obj) throws IOException {        ....    }    /**     * 反序列化取出對(duì)象     *      * @param path     *            被序列化對(duì)象保存的位置     * @return     * @throws IOException     * @throws ClassNotFoundException     */    public Object deSerialize(String path) throws IOException,            ClassNotFoundException {        ...    }}/** * Teacher類(lèi) * @author robin * @date 2014年12月18日 */class Teacher implements Serializable{        private static final long serialVersionUID = -8751853088437904443L;        private Person person;    private transient School school;    private transient double salary;        public Teacher(Person person){        this.person = person;    }        /*略去get、set,請(qǐng)自行補(bǔ)充*/}/** * School類(lèi),不可序列化 *  * @author robin * @date 2014年12月18日 */class School{    private String name;        public School(String name){        this.name = name;    }    /*略去get、set,請(qǐng)自行補(bǔ)充*/}/** * Person類(lèi),可序列化 *  * @author robin * @date 2014年12月18日 */class Person implements Serializable {     ....}

在不對(duì)Teacher類(lèi)的school成員添加transient標(biāo)識(shí)的情況下,若school值不為null,會(huì)報(bào)NotSerializableException。異常信息如下: image

在不對(duì)Teacher類(lèi)的salary成員添加transient標(biāo)識(shí)的時(shí)候,會(huì)如實(shí)輸出salary的值,添加后則只會(huì)輸出salary的默認(rèn)初始值即0.0。

image

需要注意的是transient只能修飾屬性(filed),不能修飾類(lèi)或方法。

自定義序列化

transient提供了一種簡(jiǎn)潔的方式將被transient修飾的成員屬性完全隔離在序列化機(jī)制之外。這樣子固然不錯(cuò),但是Java還提供了一種自定義序列化機(jī)制讓開(kāi)發(fā)者更自由地控制如何序列化各個(gè)成員屬性,或者不序列化某些屬性(與transient效果相同)。

在需要自定義序列化和反序列化的類(lèi)中需要提供以下方法:

  • private void writeObject(ObjectOutputStream out)
  • private void readObject(ObjectInputStream in)
  • private void readObjectNoData()

先說(shuō)下前兩個(gè)方法writeObject和readObject,這兩個(gè)方法和ObjectOutputStream及ObjectInputStream里對(duì)應(yīng)的方法名稱(chēng)相同。實(shí)際上,盡管這兩個(gè)方法是private型的,但是仍然是在被序列化(或反序列化)階段被外部類(lèi)ObjectOutputStream(或ObjectInputStream)調(diào)用。僅以序列化為例,ObjectOutputStream在執(zhí)行自己的writeObject方法前會(huì)先通過(guò)反射在要被序列化的對(duì)象的類(lèi)中(有點(diǎn)繞口是吧)查找有無(wú)自定義的writeObject方法,如有的話,則會(huì)優(yōu)先調(diào)用自定義的writeObject方法。因?yàn)椴檎曳瓷浞椒〞r(shí)使用的是getPrivateMethod,所以自定以的writeObject方法的作用域要被設(shè)置為private。通過(guò)自定義writeObject和readObject方法可以完全控制對(duì)象的序列化與反序列化。

如下是示例代碼:

package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import com.sun.xml.internal.ws.encoding.soap.DeserializationException;/** * 序列化測(cè)試類(lèi) *  * @author robin * @date 2014年12月18日 */public class SerialTest {    public static void main(String[] args) {        Person robin = new Person("robin", 29);                String savePath = "D://object.txt";        SerialTest test = new SerialTest();        try {            test.serialize(savePath, robin);            Person person = (Person) test.deSerialize(savePath);                        System.out.println("Name:" + person.getName()                             +" Age:" + person.getAge());                    } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }     }    /**     * 實(shí)現(xiàn)序列化     *      * @param obj     *            要被序列化保存的對(duì)象     * @param path     *            保存地址     * @throws IOException     */    public void serialize(String path, Person ... obj) throws IOException {        ObjectOutputStream oos = null;        ...    }    /**     * 反序列化取出對(duì)象     *      * @param path     *            被序列化對(duì)象保存的位置     * @return     * @throws IOException     * @throws ClassNotFoundException     */    public Object deSerialize(String path) throws IOException,            ClassNotFoundException {        ...    }}/** * Person類(lèi),可序列化 *  * @author robin * @date 2014年12月18日 */class Person implements Serializable {    private static final long serialVersionUID = -6412852654889352693L;    /**     * 姓名     */    private String name;    /**     * 年齡     */    private int age;    public Person() {}    public Person(String name, int age) {        this.name = name;        this.age = age;    }    /*  略去get和set,請(qǐng)自行實(shí)現(xiàn)  */            private void writeObject(ObjectOutputStream out) throws IOException{        out.writeObject(name);        out.writeInt(age + 1);                System.out.println("my write");    }        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{        this.name = "zhangsan";        this.age = 30;        System.out.println("my read");    }}

以下是輸出結(jié)果:

image

關(guān)于readObjectNoData,在網(wǎng)上找了如下一段說(shuō)明:

readObjectNoData   原始情況    pojo        public class Person implements Serializable {                           private int age;                            public Person() {  }                          //setter getter...              }    序列化         Person p = new Person();                      p.setAge(10);                      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:/person.ser"));                      oos.writeObject(p);                     oos.flush();                      oos.close();  類(lèi)結(jié)構(gòu)變化后, 序列化數(shù)據(jù)不變     pojo      Animal        implements Serializable        顯式編寫(xiě)readObjectNoData        public class Animal implements Serializable {                                  private String name;                                 public Animal() {  }                                 //setter getter...                                 private void readObjectNoData() {                                           this.name = "zhangsan";                                 }                  }      Person         extends Animal        public class Person extends Animal implements Serializable {                                  private int age;                                  public Person() {  }                                 // setter getter...                 }    反序列化      ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/person.ser"));             Person sp = (Person) ois.readObject();             System.out.println(sp.getName());      readObject時(shí), 會(huì)調(diào)用readObjectNoData

readObjectNoData在我理解看來(lái)像是一種異常處理機(jī)制,用來(lái)在序列化的流不完整的情況下返回正確的值。

使用 writeReplace和readResolve

writeReplace和readResolve是一種更徹底的序列化的機(jī)制,它甚至可以將序列化的目標(biāo)對(duì)象替換為其它的對(duì)象。

但是與writeObject和readObject不同的是,這二者不是必須要一起使用的,而且盡量應(yīng)分開(kāi)使用。若一起使用的話,只有writeReplace會(huì)生效。

代碼可以說(shuō)明一切,首先是writeReplace:

package com.zhyea.test;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.ObjectStreamException;import java.io.Serializable;/** * 序列化測(cè)試類(lèi) *  * @author robin * @date 2014年12月18日 */public class SerialTest {    public static void main(String[] args) {        Person robin = new Person("robin", 29);                String savePath = "D://object.txt";        SerialTest test = new SerialTest();        try {            //序列化            test.serialize(savePath, robin);            //反序列化            String person = (String) test.deSerialize(savePath);                        System.out.println(person);                    } catch (IOException e) {            e.printStackTrace();        } catch (ClassNotFoundException e) {            e.printStackTrace();        }     }    /**     * 實(shí)現(xiàn)序列化     *      * @param obj     *            要被序列化保存的對(duì)象     * @param path     *            保存地址     * @throws IOException     */    public void serialize(String path, Person ... obj) throws IOException {        ObjectOutputStream oos = null;        ....    }    /**     * 反序列化取出對(duì)象     *      * @param path     *            被序列化對(duì)象保存的位置     * @return     * @throws IOException     * @throws ClassNotFoundException     */    public Object deSerialize(String path) throws IOException,            ClassNotFoundException {        ....    }}/** * Person類(lèi),可序列化 *  * @author robin * @date 2014年12月18日 */class Person implements Serializable {    private static final long serialVersionUID = -6412852654889352693L;    /**     * 姓名     */    private String name;    /**     * 年齡     */    private int age;    public Person() {}    public Person(String name, int age) {        this.name = name;        this.age = age;    }    /*   set和get方法請(qǐng)自行添加    */            private Object writeReplace() throws ObjectStreamException{        System.out.println("my writeReplace");        return "robin";    }        private Object readResolve() throws ObjectStreamException{        System.out.println("my readResolve");        return "zhangsan";    }        private void writeObject(ObjectOutputStream out) throws IOException{        ....    }        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{        ....    }}

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

image

在Person類(lèi)中,保留了之前的writeObject和readObject方法,并且還添加了readResolve方法。但是從運(yùn)行結(jié)果可以看出來(lái),這3個(gè)方法都沒(méi)有被調(diào)用,只有writeReplace方法被使用。可以理解為當(dāng)使用writeReplace時(shí),其他的自定義方法都不會(huì)被調(diào)用,即writeReplace的優(yōu)先級(jí)最高。

現(xiàn)在注釋掉writeReplace方法,再次執(zhí)行,結(jié)果如下:

image

這次writeObject,readObject和readResolve方法都被調(diào)用。readResolve方法緊跟著readObject方法被調(diào)用且最終返回的值是readResolve返回的值,readObject里反序列化生成的對(duì)象被拋棄。

此外還有一點(diǎn)需要說(shuō)明:writeReplace和readResolve可以使用任何作用域,這意味著子類(lèi)也可以調(diào)用超類(lèi)的這兩個(gè)方法。但是如果子類(lèi)還有不同的序列化及反序列化需求,這就需要子類(lèi)重寫(xiě)這個(gè)方法,有些時(shí)候這樣做是沒(méi)有必要的。因此一般情況下將這兩個(gè)方法的作用域設(shè)置為private。

使用Externalizable

一開(kāi)始有提到過(guò)實(shí)現(xiàn)Externalizable接口也可以實(shí)現(xiàn)類(lèi)的序列化。使用這種方法,可以由開(kāi)發(fā)者完全決定如何序列化和反序列化目標(biāo)對(duì)象。Externalizable接口提供了writeExternal和readExternal兩個(gè)方法。

實(shí)際上這種方法和前面的自定義序列化方法很相似,只是Externalizable強(qiáng)制自定義序列化。在使用了Externalizable的類(lèi)中仍可以使用writeReplace和readResolve方法。使用Externalizable進(jìn)行序列化較之使用Serializable性能略好,但是復(fù)雜度較高。

版本問(wèn)題

執(zhí)行序列化和反序列化時(shí)有可能會(huì)遇到JRE版本問(wèn)題。尤其是在網(wǎng)絡(luò)的兩端進(jìn)行通信時(shí),這種情況更為多見(jiàn)。

為了解決這種問(wèn)題,Java允許為序列化的類(lèi)提供一個(gè)serialVersionUID的常量標(biāo)識(shí)該類(lèi)的版本。只要serialVersionUID的值不變,Java就會(huì)把它們當(dāng)作相同的序列化版本。

如果不顯式定義serialVersionUID,那么JVM就會(huì)計(jì)算出一個(gè)serialVersionUID的值。不同的編譯器下會(huì)產(chǎn)生不同的serialVersionUID值。serialVersionUID值不同則會(huì)導(dǎo)致編譯失敗。可以使用jdk的bin目錄下的serial.exe查看可序列化類(lèi)的serialVersionUID,指令如下:

serial Person

如果對(duì)類(lèi)的修改確實(shí)會(huì)導(dǎo)致反序列化失敗,則應(yīng)主動(dòng)調(diào)整serialVersionUID的值。導(dǎo)致類(lèi)的反序列化失敗的修改有以下幾種情形:

  • 只是修改了類(lèi)的方法,不會(huì)影響反序列化。
  • 只是修改了類(lèi)的static Field或transient Field,不會(huì)影響反序列化。
  • 修改了類(lèi)的非static和非transient Field,會(huì)影響序列化。
序列化注意事項(xiàng)

關(guān)于對(duì)象的序列化,總結(jié)下注意事項(xiàng):

  • 對(duì)象的類(lèi)名、Field(包括基本類(lèi)型、數(shù)組及對(duì)其他對(duì)象的引用)都會(huì)被序列化,對(duì)象的static Field,transient Field及方法不會(huì)被序列化;
  • 實(shí)現(xiàn)Serializable接口的類(lèi),如不想某個(gè)Field被序列化,可以使用transient關(guān)鍵字進(jìn)行修飾;
  • 保證序列化對(duì)象的引用類(lèi)型Filed的類(lèi)也是可序列化的,如不可序列化,可以使用transient關(guān)鍵字進(jìn)行修飾,否則會(huì)序列化失敗;
  • 反序列化時(shí)必須要有序列化對(duì)象的類(lèi)的class文件;
  • 當(dāng)通過(guò)文件網(wǎng)絡(luò)讀取序列化對(duì)象的時(shí)候,必需按寫(xiě)入的順序來(lái)讀取。

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 鹤山市| 溆浦县| 上栗县| 阿拉善右旗| 太仓市| 宁城县| 新龙县| 岢岚县| 汉源县| 乌拉特后旗| 南和县| 岳西县| 德兴市| 无棣县| 丹江口市| 闵行区| 哈尔滨市| 法库县| 定边县| 綦江县| 莱芜市| 屯留县| 泾源县| 崇义县| 三原县| 绥棱县| 合川市| 亚东县| 南京市| 滁州市| 泰顺县| 徐水县| 开鲁县| 甘孜| 桐庐县| 聂荣县| 开封市| 县级市| 徐闻县| 马龙县| 高淳县|