成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么遠(yuǎn)程傳輸對(duì)象要序列化?

開(kāi)發(fā) 前端
對(duì)象的序列化,在實(shí)際的開(kāi)發(fā)過(guò)程中,使用的非常頻繁,尤其是微服務(wù)開(kāi)發(fā),如果你用的是SpringBoot + Dubbo?組合的框架,那么在通過(guò)rpc調(diào)用的時(shí)候,如果傳輸?shù)膶?duì)象沒(méi)有實(shí)現(xiàn)序列化,會(huì)直接報(bào)錯(cuò)!

01、背景介紹

序列化和反序列化幾乎是工程師們每天都需要面對(duì)的事情,尤其是當(dāng)前流行的微服務(wù)開(kāi)發(fā)。

光看定義上,對(duì)于初學(xué)者來(lái)說(shuō),可能很難一下子理解序列化的意義,尤其是面對(duì)這種特別學(xué)術(shù)詞語(yǔ)的時(shí)候,內(nèi)心會(huì)不由自主的發(fā)問(wèn):它到底是啥,用來(lái)干嘛的?

如果用通俗的方式來(lái)理解,你可以用變魔術(shù)的方式來(lái)理解它,就好比你想把一件鐵器從一個(gè)地方運(yùn)往到另一個(gè)地方,在出發(fā)的時(shí)候,通過(guò)魔術(shù)方式將這個(gè)東西融化成一桶鐵水,當(dāng)?shù)竭_(dá)目的地之后,又通過(guò)變魔術(shù)的方式,將這桶鐵水還原成一件鐵器。當(dāng)鐵器變成鐵水的過(guò)程,可以理解為序列化;從鐵水變成鐵器,可以理解為反序列化。

站在程序世界的角度看,我們都知道計(jì)算機(jī)之間傳遞信息的最小單元是字節(jié)流,序列化其實(shí)就是將一個(gè)對(duì)象變成所有的計(jì)算機(jī)都能識(shí)別的字節(jié)流;反序列化就是將接受到的字節(jié)流還原成一個(gè)程序能識(shí)別的對(duì)象。

簡(jiǎn)單的說(shuō),序列化最終的目的是為了對(duì)象可以更方面的進(jìn)行跨平臺(tái)存儲(chǔ)和進(jìn)行網(wǎng)絡(luò)傳輸。

基本上只要是涉及到跨平臺(tái)存儲(chǔ)或者進(jìn)行網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù),都需要進(jìn)行序列化。

互聯(lián)網(wǎng)早期的序列化方式主要有COM和CORBA。

COM主要用于Windows平臺(tái),并沒(méi)有真正實(shí)現(xiàn)跨平臺(tái),另外COM的序列化的原理利用了編譯器中虛表,使得其學(xué)習(xí)成本巨大(想一下這個(gè)場(chǎng)景, 工程師需要是簡(jiǎn)單的序列化協(xié)議,但卻要先掌握語(yǔ)言編譯器)。由于序列化的數(shù)據(jù)與編譯器緊耦合,擴(kuò)展屬性非常麻煩。

CORBA是早期比較好的實(shí)現(xiàn)了跨平臺(tái),跨語(yǔ)言的序列化協(xié)議。COBRA的主要問(wèn)題是參與方過(guò)多帶來(lái)的版本過(guò)多,版本之間兼容性較差,以及使用復(fù)雜晦澀。這些政治經(jīng)濟(jì),技術(shù)實(shí)現(xiàn)以及早期設(shè)計(jì)不成熟的問(wèn)題,最終導(dǎo)致COBRA的漸漸消亡。J2SE 1.3之后的版本提供了基于CORBA協(xié)議的RMI-IIOP技術(shù),這使得Java開(kāi)發(fā)者可以采用純粹的Java語(yǔ)言進(jìn)行CORBA的開(kāi)發(fā)。

隨著軟件技術(shù)的快速發(fā)展,之后逐漸出現(xiàn)了比較流行的序列化方式,例如:XML、JSON、Protobuf、Thrift 和 Avro等等。

這些序列化方式各有千秋,不能簡(jiǎn)單的說(shuō)哪一種序列化方式是最好的,只能從你的當(dāng)時(shí)環(huán)境下去選擇最適合你的序列化方式,如果你要為你的公司項(xiàng)目進(jìn)行序列化技術(shù)的選型,主要可以從以下幾個(gè)方面進(jìn)行考慮:

  • 是否支持跨平臺(tái):尤其是多種語(yǔ)言混合開(kāi)發(fā)的項(xiàng)目,是否支持跨平臺(tái)直接決定了系統(tǒng)開(kāi)發(fā)難度
  • 序列化的速度:速度快的方式會(huì)為你的系統(tǒng)性能提升不少
  • 序列化出來(lái)的大小:數(shù)據(jù)越小越好,小的數(shù)據(jù)傳輸快,也不占帶寬,也能整體提升系統(tǒng)的性能

BB了這么多,作為一名 java 程序員,我們應(yīng)該如何使用序列化呢,以及序列化的過(guò)程中應(yīng)該需要注意的問(wèn)題。

下面,我們一起來(lái)了解一下!

02、代碼實(shí)踐

java 實(shí)現(xiàn)序列化方式非常簡(jiǎn)單,只需要實(shí)現(xiàn)Serializable接口即可,例如下面這個(gè)類(lèi)。

public class Student implements Serializable {

    /**
     * 用戶名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Student1{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

我們來(lái)測(cè)試一下,將Student對(duì)象進(jìn)行二進(jìn)制的數(shù)據(jù)存儲(chǔ)后,并從文件中讀取數(shù)據(jù)出來(lái)轉(zhuǎn)成Student對(duì)象,這個(gè)過(guò)程其實(shí)就是一個(gè)序列化和反序列化的過(guò)程。

public class ObjectMainTest {

    public static void main(String[] args) throws Exception {
        //序列化
        serializeAnimal();
        //反序列化
        deserializeAnimal();
    }

    private static void serializeAnimal() throws Exception {
        Student black = new Student("張三", 20);
        System.out.println(black.toString());
        System.out.println("=================開(kāi)始序列化================");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.log"));
        oos.writeObject(black);
        oos.flush();
        oos.close();
    }

    private static void deserializeAnimal() throws Exception {
        System.out.println("=================開(kāi)始反序列化================");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.log"));
        Student black = (Student) ois.readObject();
        ois.close();
        System.out.println(black.toString());
    }
}

輸出結(jié)果:

Student{name='張三', age=20}
=================開(kāi)始序列化================
=================開(kāi)始反序列化================
Student{name='張三', age=20}

看起來(lái)是不是超級(jí)簡(jiǎn)單,但是請(qǐng)你別大意,這里面的坑還真不少,請(qǐng)看下面的問(wèn)題匯總!

03、序列化問(wèn)題匯總

3.1、static 屬性不能被序列化

實(shí)際在序列化的時(shí)候,被static修飾的屬性字段是不能被序列化進(jìn)去的,因?yàn)殪o態(tài)變量屬于類(lèi)的狀態(tài),序列化并不保存靜態(tài)變量!

3.2、Transient 屬性不會(huì)被序列化

被Transient修飾的屬性無(wú)法被序列化,眼見(jiàn)為實(shí),我們給Student類(lèi)的name字段加一個(gè)transient修飾符。

public class Student implements Serializable {

    /**
     * 用戶名
     */
    private transient String name;

    //...省略
}

運(yùn)行測(cè)試方法,輸出結(jié)果如下:

Student{name='張三', age=20}
=================開(kāi)始序列化================
=================開(kāi)始反序列化================
Student{name='null', age=20}

很明顯,被transient修飾的name屬性,反序列化后的結(jié)果為null。

3.3、序列化版本號(hào) serialVersionUID 問(wèn)題

只要是實(shí)現(xiàn)了Serializable接口的類(lèi)都會(huì)有一個(gè)版本號(hào),如果我們沒(méi)有定義,JDK 工具會(huì)按照我們對(duì)象的屬性生成一個(gè)對(duì)應(yīng)的版本號(hào),當(dāng)然我們還可以自定義,例如給Student類(lèi)自定義一個(gè)序列化版本號(hào),操作如下。

public class Student implements Serializable {

    //自定義序列化版本號(hào)
   private static final long serialVersionUID = 1l;

    //...省略
}

如何驗(yàn)證這一點(diǎn)呢?

首先,我們先序列化一個(gè)Student對(duì)象,里面沒(méi)有自定義版本號(hào),然后在反序列化的時(shí)候,我們給這個(gè)對(duì)象自定義一個(gè)版本號(hào),運(yùn)行測(cè)試程序,看能不能反序列化成功?

Exception in thread "main" java.io.InvalidClassException: com.example.java.serializable.test1.entity.Student; local class incompatible: stream classdesc serialVersionUID = 821478144412499207, local class serialVersionUID = 1
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)

答案很明顯,反序列化失敗!

分析原因:Student對(duì)象序列化時(shí)的版本號(hào)是821478144412499207,反序列化時(shí)的版本號(hào)是1,兩者不一致,導(dǎo)致無(wú)法反序列化成功!

當(dāng)我們沒(méi)有顯式的自定義序列化版本號(hào)時(shí),JDK 會(huì)根據(jù)當(dāng)前對(duì)象的屬性自動(dòng)生成一個(gè)對(duì)象的版本號(hào),只要對(duì)象的屬性不會(huì)發(fā)生變化,這個(gè)版本號(hào)也基本上不會(huì)發(fā)生變化,但是當(dāng)對(duì)象的屬性發(fā)生了變化,對(duì)應(yīng)的反序列化對(duì)象沒(méi)有跟著一起變化,大概率會(huì)出現(xiàn)反序列化失敗!

為了眼見(jiàn)為實(shí),我們繼續(xù)以實(shí)際案例給大家演示一下。

還是以上面那個(gè)為主,我們先序列化一個(gè)Student對(duì)象,里面沒(méi)有自定義版本號(hào),然后在反序列化操作的時(shí)候,我們給Student對(duì)象新增一個(gè)屬性email,同時(shí)也不自定義版本號(hào)。

public class Student implements Serializable {

    /**
     * 用戶名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    /**
     * 郵箱
     */
    private String email;
    
    //省略set、get...
}

看看運(yùn)行效果:

Exception in thread "main" java.io.InvalidClassException: com.example.java.serializable.test1.entity.Student; local class incompatible: stream classdesc serialVersionUID = 821478144412499207, local class serialVersionUID = -5996907635197467174
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)

答案很顯然,反序列化報(bào)錯(cuò)了!兩者的版本號(hào)不一致!

在平時(shí)開(kāi)發(fā)的過(guò)程中,實(shí)體類(lèi)的屬性難免會(huì)發(fā)生改動(dòng),我們有些同學(xué)啊,在寫(xiě)代碼的時(shí)候只是把序列化的接口實(shí)現(xiàn)了,但是沒(méi)有自定義版本號(hào),在這點(diǎn)上,我強(qiáng)烈建議大家一定要給每個(gè)實(shí)現(xiàn)了Serializable接口的類(lèi),自定義一個(gè)版本號(hào),即使對(duì)象的屬性發(fā)生了變化,也不會(huì)影響到數(shù)據(jù)的序列化和反序列化操作!

操作很簡(jiǎn)單,直接在實(shí)體類(lèi)里面加上這個(gè)靜態(tài)變量即可!

//自定義序列化版本號(hào)
private static final long serialVersionUID = 1l;

3.4、父類(lèi)、子類(lèi)序列化問(wèn)題

在實(shí)際的開(kāi)發(fā)過(guò)程中,尤其是實(shí)體類(lèi),為了對(duì)象屬性的復(fù)用,我們往往會(huì)采用繼承的方式來(lái)處理。

使用了繼承之后,父類(lèi)屬性是否可以正常被序列化呢?下面我們一起來(lái)看看!

  • 父類(lèi)沒(méi)有實(shí)現(xiàn)序列化,子類(lèi)實(shí)現(xiàn)序列化

首先我們創(chuàng)建兩個(gè)類(lèi)Parent和Child,Child繼承自Parent。

public class Parent {

    private String name;

    public String getName() {
        return name;
    }

    public Parent setName(String name) {
        this.name = name;
        return this;
    }

}
public class Child extends Parent implements Serializable{


    private static final long serialVersionUID = 1l;

    private String id;

    public String getId() {
        return id;
    }

    public Child setId(String id) {
        this.id = id;
        return this;
    }
    
}

編寫(xiě)測(cè)試類(lèi),先序列化,然后再反序列化!

public class ObjectMainTest {

    public static void main(String[] args) throws Exception {
        serializeAnimal();
        deserializeAnimal();
    }

    private static void serializeAnimal() throws Exception {
        Child black = new Child();
        black.setId("123");
        black.setName("張三");
        System.out.println("id:" +  black.getId() + ",name:" +  black.getName());
        System.out.println("=================開(kāi)始序列化================");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.log"));
        oos.writeObject(black);
        oos.flush();
        oos.close();
    }

    private static void deserializeAnimal() throws Exception {
        System.out.println("=================開(kāi)始反序列化================");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.log"));
        Child black = (Child) ois.readObject();
        ois.close();
        System.out.println("id:" +  black.getId() + ",name:" +  black.getName());
    }
}

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

id:123,name:張三
=================開(kāi)始序列化================
=================開(kāi)始反序列化================
id:123,name:null

結(jié)果很明顯,父類(lèi)的屬性沒(méi)有被序列化進(jìn)去!

我們?cè)趤?lái)試試,另一種常見(jiàn)

  • 父類(lèi)實(shí)現(xiàn)序列化,子類(lèi)不實(shí)現(xiàn)序列化
public class Parent implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public String getName() {
        return name;
    }

    public Parent setName(String name) {
        this.name = name;
        return this;
    }

}
public class Child extends Parent {

    private String id;

    public String getId() {
        return id;
    }

    public Child setId(String id) {
        this.id = id;
        return this;
    }

}

接著運(yùn)行一次程序,結(jié)果如下!

id:123,name:張三
=================開(kāi)始序列化================
=================開(kāi)始反序列化================
id:123,name:張三

結(jié)果很明顯,父類(lèi)的屬性被序列化進(jìn)去!

假如,子類(lèi)和父類(lèi),都實(shí)現(xiàn)了序列化,并且序列化版本號(hào)都不一樣,會(huì)不會(huì)出現(xiàn)問(wèn)題呢?

  • 父類(lèi)實(shí)現(xiàn)序列化,子類(lèi)實(shí)現(xiàn)序列化
public class Parent implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public String getName() {
        return name;
    }

    public Parent setName(String name) {
        this.name = name;
        return this;
    }

}
public class Child extends Parent implements Serializable{

    private static final long serialVersionUID = 2l;

    private String id;

    public String getId() {
        return id;
    }

    public Child setId(String id) {
        this.id = id;
        return this;
    }

}

運(yùn)行一次程序,結(jié)果如下!

id:123,name:張三
=================開(kāi)始序列化================
=================開(kāi)始反序列化================
id:123,name:張三

父類(lèi)的屬性序列化依然成功,當(dāng)父、子類(lèi)都實(shí)現(xiàn)了序列化,并且定義了不同的版本號(hào),這種情況下,版本號(hào)是跟著子類(lèi)的版本號(hào)走的!

總結(jié)起來(lái),當(dāng)父類(lèi)實(shí)現(xiàn)序列化時(shí),子類(lèi)所有的屬性也會(huì)全部被序列化;但是當(dāng)父類(lèi)沒(méi)有實(shí)現(xiàn)序列化,子類(lèi)在序列化時(shí),父類(lèi)屬性并不會(huì)被序列化!

3.5、自定義序列化過(guò)程

Serializable接口內(nèi)部序列化是 JVM 自動(dòng)實(shí)現(xiàn)的,但是在某些少數(shù)的場(chǎng)景下,你可能想自定義序列化和反序列化的內(nèi)容,但是又不想改實(shí)體類(lèi)屬性,這個(gè)時(shí)候你可以采用自定義序列化的實(shí)現(xiàn)方式。

自定義序列化方式,其實(shí)也很簡(jiǎn)單,只需要實(shí)現(xiàn) JDK 自身提供的Externalizable接口就行,里面有兩個(gè)核心方法,一個(gè)是數(shù)據(jù)寫(xiě)入,另一個(gè)是數(shù)據(jù)的讀取。

public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Externalizable接口的實(shí)現(xiàn)過(guò)程也很簡(jiǎn)單,我們創(chuàng)建一個(gè)Person,實(shí)現(xiàn)自Externalizable的兩個(gè)方法。

public class Person implements Externalizable {

    private static final long serialVersionUID = 1l;

    private String name;

    private int age;

    /**
     * 實(shí)現(xiàn)了Externalizable這個(gè)接口時(shí)需要提供無(wú)參構(gòu)造,在反序列化時(shí)會(huì)檢測(cè)
     */
    public Person() {
        System.out.println("Person: empty");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("person writeExternal...");
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
        System.out.println("person readExternal...");
        name = (String) in.readObject();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

測(cè)試Person對(duì)象的序列化和反序列化。

public class ExternalizableMain {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serializable();
        deserializable();
    }

    private static void serializable() throws IOException {
        Person person = new Person("張三", 15);
        System.out.println(person.toString());
        System.out.println("=================開(kāi)始序列化================");
        FileOutputStream boas = new FileOutputStream("person.log");
        ObjectOutputStream oos = new ObjectOutputStream(boas);
        oos.writeObject(person);
        oos.close();
        boas.close();
    }

    private static void deserializable() throws IOException, ClassNotFoundException {
        System.out.println("============反序列化=============");
        ObjectInputStream bis = new ObjectInputStream(new FileInputStream("person.log"));
        Person person = (Person)bis.readObject();
        System.out.println(person.toString());
    }
}

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

Person{name='張三', age=15}
=================開(kāi)始序列化================
person writeExternal...
============反序列化=============
Person: empty
person readExternal...
Person{name='張三', age=15}

04、小結(jié)

對(duì)象的序列化,在實(shí)際的開(kāi)發(fā)過(guò)程中,使用的非常頻繁,尤其是微服務(wù)開(kāi)發(fā),如果你用的是SpringBoot + Dubbo組合的框架,那么在通過(guò)rpc調(diào)用的時(shí)候,如果傳輸?shù)膶?duì)象沒(méi)有實(shí)現(xiàn)序列化,會(huì)直接報(bào)錯(cuò)!

在使用序列化的時(shí)候,坑點(diǎn)還不少,尤其是版本號(hào)的問(wèn)題,這個(gè)很容易被忽略,大家在實(shí)際開(kāi)發(fā)的時(shí)候,強(qiáng)烈推薦自定義版本號(hào),這樣可以避免傳輸?shù)膶?duì)象屬性發(fā)生變化的時(shí)候,接口反序列化出錯(cuò)的概率!

05、參考

1、https://www.huaweicloud.com/articles/6b6d1d97c0a9155899f0f7354c86610d.html

2、https://zhuanlan.zhihu.com/p/40462507

3、https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html

責(zé)任編輯:武曉燕 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2011-06-01 15:18:43

Serializabl

2025-05-08 08:30:00

Redis自定義序列化數(shù)據(jù)庫(kù)

2025-03-05 10:49:32

2025-04-30 10:49:11

Java序列化反序列化

2011-06-01 15:05:02

序列化反序列化

2018-03-19 10:20:23

Java序列化反序列化

2009-06-14 22:01:27

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

2012-04-13 10:45:59

XML

2009-09-09 15:47:27

XML序列化和反序列化

2009-09-09 14:45:41

XML序列化和反序列化

2022-08-06 08:41:18

序列化反序列化Hessian

2009-03-10 13:38:01

Java序列化字節(jié)流

2020-12-24 18:46:11

Java序列化編程語(yǔ)言

2023-03-09 08:23:07

序列化?接口方法

2010-03-19 16:38:29

Java Socket

2023-09-12 07:24:07

Java序列化接口

2021-08-30 12:25:12

Python序列化函數(shù)

2012-02-14 10:29:02

Java

2009-08-25 15:15:08

C#對(duì)象序列化應(yīng)用

2009-08-24 17:14:08

C#序列化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 日韩成人在线视频 | 免费看a | 国产情侣久久 | 久久69精品久久久久久久电影好 | 国产精品国产a级 | 国产精品自拍av | 99亚洲精品视频 | 亚洲在线免费 | 成人毛片在线视频 | 成人在线不卡 | 国产精品美女在线观看 | 日日操操 | 国产精品久久久久久久久久久久午夜片 | 国产亚洲精品久久久久久牛牛 | av性色全交蜜桃成熟时 | 三级视频久久 | 欧美三区视频 | 夜色www国产精品资源站 | 国产精品亚洲一区 | 欧美专区在线 | 欧美乱淫视频 | 国产午夜视频 | 精品成人免费一区二区在线播放 | 91伦理片 | 91性高湖久久久久久久久_久久99 | 国产91九色 | 欧美1页| 中文字幕视频在线 | www.日本三级| 国产精品99久久久久久宅男 | 欧洲亚洲一区 | 亚洲欧洲日韩精品 中文字幕 | 91原创视频| 大伊人久久 | 久久精品99| 欧美成年网站 | 日韩精品视频在线免费观看 | 国产精品亚洲成在人线 | 欧美日韩在线播放 | 亚洲欧洲在线观看视频 | 久久av资源网 |