国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

從淺入深理解序列化和反序列化

這篇具有很好參考價值的文章主要介紹了從淺入深理解序列化和反序列化。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

什么是java序列化

序列化:把對象轉(zhuǎn)換為字節(jié)序列的過程

反序列:把字節(jié)序列恢復(fù)為對象的過程

對象序列化機制(object serialization)是java語言內(nèi)建的一種對象持久化方式,通過對象序列化,可以將對象的狀態(tài)信息保存為字節(jié)數(shù)組,并且可以在有需要的時候?qū)⑦@個字節(jié)數(shù)組通過反序列化的方式轉(zhuǎn)換成對象,對象的序列化可以很容易的在JVM中的活動對象和字節(jié)數(shù)組(流)之間進行轉(zhuǎn)換。

簡單說:對象序列化成的字節(jié)序列會包含對象的類型信息、對象的數(shù)據(jù)等,說白了就是包含了描述這個對象的所有信息,能根據(jù)這些信息“復(fù)刻”出一個和原來一模一樣的對象。

在代碼運行的時候,我們可以看到很多的對象,可以是一個,也可以是一類對象的集合,很多的對象數(shù)據(jù),這些數(shù)據(jù)中,有些信息我們想讓他持久的保存起來,那么這個序列化,就是把內(nèi)存里面的這些對象給變成一連串的字節(jié)描述的過程。

常見的就是變成文件

什么情況需要使用 Java 序列化

想把的內(nèi)存中的對象狀態(tài)保存到一個文件中或者數(shù)據(jù)庫中時候;

想用套接字在網(wǎng)絡(luò)上傳送對象的時候;

想通過RMI(遠程方法調(diào)用)傳輸對象的時候。

為什么要序列化

Java對象是運行在JVM的堆內(nèi)存中的,如果JVM停止后,它的生命也就戛然而止。

從淺入深理解序列化和反序列化

如果想在JVM停止后,把這些對象保存到磁盤或者通過網(wǎng)絡(luò)傳輸?shù)搅硪贿h程機器,怎么辦呢?磁盤這些硬件可不認(rèn)識Java對象,它們只認(rèn)識二進制這些機器語言,所以我們就要把這些對象轉(zhuǎn)化為字節(jié)數(shù)組,這個過程就是序列化啦~

序列化和反序列化過程如下

從淺入深理解序列化和反序列化

因為在網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)只能是二進制。

序列化就是將對象轉(zhuǎn)換成二進制,反序列化就是講二進制轉(zhuǎn)化為對象的過程。

RPC 框架為什么需要序列化

因為網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)必須是二進制數(shù)據(jù),所以在 RPC 調(diào)用中,對入?yún)ο笈c返回值對象進行序列化與反序列化是一個必須的過程。

RPC 的通信流程

從淺入深理解序列化和反序列化

序列化用途

序列化使得對象可以脫離程序運行而獨立存在,它主要有兩種用途

序列化機制可以讓對象地保存到硬盤上,減輕內(nèi)存壓力的同時,也起了持久化的作用

比如 Web服務(wù)器中的Session對象,當(dāng)有 10+萬用戶并發(fā)訪問的,就有可能出現(xiàn)10萬個Session對象,內(nèi)存可能消化不良,于是Web容器就會把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對象還原到內(nèi)存中。

序列化機制讓Java對象可以在網(wǎng)絡(luò)傳輸

我們在使用Dubbo遠程調(diào)用服務(wù)框架時,需要把傳輸?shù)腏ava對象實現(xiàn)Serializable接口,即讓Java對象序列化,因為這樣才能讓對象在網(wǎng)絡(luò)上傳輸。

實現(xiàn)對象序列化需要做哪些工作

JAVA提供了API實現(xiàn)了對象的序列化和反序列化的功能,使用這些API時需要遵守如下約定:

被序列化的對象類型需要實現(xiàn)序列化接口,此接口是標(biāo)志接口,沒有聲明任何的抽象方法,JAVA編譯器識別這個接口,自動的為這個類添加序列化和反序列化方法。

為了保持序列化過程的穩(wěn)定,建議在類中添加序列化版本號。

不想讓字段放在硬盤上就加transient

Java序列化常用API

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable
Serializable 接口

Serializable接口是一個標(biāo)記接口,沒有方法或字段。一旦實現(xiàn)了此接口,就標(biāo)志該類的對象就是可序列化的。

public interface Serializable {
}
Externalizable 接口

Externalizable繼承了Serializable接口,還定義了兩個抽象方法:writeExternal()和readExternal(),如果開發(fā)人員使用Externalizable來實現(xiàn)序列化和反序列化,需要重寫writeExternal()和readExternal()方法

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
java.io.ObjectOutputStream類

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

java.io.ObjectInputStream

表示對象輸入流,它的readObject()方法,從輸入流中讀取到字節(jié)序列,反序列化成為一個對象,最后將其返回。

如何實現(xiàn)對象的序列化

  1. 對于要序列化對象的類要去實現(xiàn)Serializable接口或者Externalizable接口
  2. JDK提供的ObjectOutputStream類的writeObject方法,實現(xiàn)序列化
  3. JDK提供的ObjectInputStream類的readObject方法,實現(xiàn)反序列化
Serializable 實現(xiàn)序列化
public class TestBean implements Serializable {
    private Integer id;
    private String name;
    private Date date;
    //省去getter和setter方法和toString
}

序列化寫入文本,執(zhí)行后可以在test.txt文件中看到序列化內(nèi)容

public static void main(String[] args) {
    TestBean testBean = new TestBean();
    testBean.setDate(new Date());
    testBean.setId(1);
    testBean.setName("zll1");
    //使用ObjectOutputStream序列化testBean對象并將其序列化成的字節(jié)序列寫入test.txt文件
    try (FileOutputStream fileOutputStream = new FileOutputStream("D:\\test.txt");
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);) {
        objectOutputStream.writeObject(testBean);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

反序列化:

public static void main(String[] args) {
    try (FileInputStream fileInputStream = new FileInputStream("D:\\test.txt");
         ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream)) {
        TestBean testBean = (TestBean) objectInputStream.readObject();
        System.out.println(testBean);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

輸出結(jié)果

TestBean{id=1, name='zll1', date=Fri Nov 27 14:52:48 CST 2020}

注意

  1. 一個對象要進行序列化,如果該對象成員變量是引用類型的,那這個引用類型也一定要是可序列化的,否則會報錯
  2. 同一個對象多次序列化成字節(jié)序列,這多個字節(jié)序列反序列化成的對象還是一個(使用==判斷為true)(因為所有序列化保存的對象都會生成一個序列化編號,當(dāng)再次序列化時回去檢查此對象是否已經(jīng)序列化了,如果是,那序列化只會輸出上個序列化的編號)
  3. 如果序列化一個可變對象,序列化之后,修改對象屬性值,再次序列化,只會保存上次序列化的編號(這是個坑注意下)
  4. 對于不想序列化的字段可以再字段類型之前加上transient關(guān)鍵字修飾(反序列化時會被賦予默認(rèn)值)
實現(xiàn)Externalizable接口

實現(xiàn)Externalizable接口必須重寫連個方法

  • writeExternal(ObjectOutput out)
  • readExternal(ObjectInput in)
writeObject 和 readObject 自定義序列化策略,代碼示例
public class TextBean implements Externalizable {

    private Integer id;
    private String name;
    private Date date;
   
    //可以自定義決定那些需要序列化
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(id);
        out.writeObject(name);
        out.writeObject(date);
    }
    //可以自定義決定那些需要反序列化
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readInt();
        this.name = (String) in.readObject();
        this.date = (Date) in.readObject();
    }
    //省去getter和setter方法和toString
}

序列化:

public static void main(String[] args) {
    TextBean textBean = new TextBean();
    textBean.setDate(new Date());
    textBean.setId(1);
    textBean.setName("zll1");

    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:\\externalizable.txt"))) {
        outputStream.writeObject(textBean);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

反序列化:

public static void main(String[] args) {
    try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\externalizable.txt"))) {
        TextBean textBean = (TextBean) objectInputStream.readObject();
        System.out.println(textBean);
        //輸出結(jié)果:TextBean{id=1, name='zll1', date=Fri Nov 27 16:49:17 CST 2020}
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

實現(xiàn)了 Serializable 接口是自動序列化的,實現(xiàn) Externalizable 則需要手動序列化,通過 writeExternal 和 readExternal 方法手動進行

注意

  1. 序列化對象要提供無參構(gòu)造
  2. 如果序列化時一個字段沒有序列化,那反序列化是要注意別給為序列化的字段反序列化了

實現(xiàn)序列化代碼還可參考該博客說明,關(guān)于 serialVersionUID序列化中有為什么要加一串序列ID,下方博客有寫,為了解決序列化和反序列化的算法一致性,一般1L就可以。注意 靜態(tài)static的屬性,他不序列化

(47條消息) 什么是序列化,怎么序列化,為什么序列化,反序列化會遇到什么問題,如何解決。_老周聊架構(gòu)的博客-CSDN博客

什么是 transient?

被 transient 修飾的變量不能被序列化。

1)transient修飾的變量不能被序列化;

2)transient只作用于實現(xiàn) Serializable 接口;

3)transient只能用來修飾普通成員變量字段;

4)不管有沒有 transient 修飾,靜態(tài)變量都不能被序列化;

什么是serialVersionUID

serialVersionUID 表面意思就是序列化版本號ID,其實每一個實現(xiàn)Serializable接口的類,都有一個表示序列化版本標(biāo)識符的靜態(tài)變量,或者默認(rèn)等于1L,或者等于對象的哈希碼。

serialVersionUID的作用

先講述下序列化的過程:在進行序列化時,(JAVA的話,是JVM)會把當(dāng)前類的serialVersionUID寫入到字節(jié)序列中(也會寫入序列化的文件中),在反序列化時會將字節(jié)流中的serialVersionUID同本地對象中的serialVersionUID進行對比,一直的話進行反序列化,不一致則失敗報錯(報InvalidCastException異常)

故,JAVA序列化的機制是通過判斷類的serialVersionUID來驗證版本是否一致的。

阿里開發(fā)手冊,強制要求序列化類新增屬性時,不能修改serialVersionUID字段

從淺入深理解序列化和反序列化

serialVersionUID的生成有哪三種方式

private static final long serialVersionUID= XXXL :

  1. 顯式聲明:默認(rèn)的1L
  2. 顯式聲明:根據(jù)包名、類名、繼承關(guān)系、非私有的方法和屬性以及參數(shù)、返回值等諸多因素計算出的64位的hash值
  3. 隱式聲明:未顯式的聲明serialVersionUID時java序列化機制會根據(jù)Class自動生成一個serialVersionUID(最好不要這樣,因為如果Class發(fā)生變化,自動生成的serialVersionUID可能會隨之發(fā)生變化,導(dǎo)致匹配不上)
序列化類增加屬性時,最好不要修改serialVersionUID,避免反序列化失敗

IDEA中新建Class可以在類名上按alt+enter:

從淺入深理解序列化和反序列化

如果不顯示上圖提示,可以按照下面步驟設(shè)置:

從淺入深理解序列化和反序列化

注意

不同的serialVersionUID的值,會影響到反序列化,也就是數(shù)據(jù)的讀取,你寫1L,注意L大些。計算機是不區(qū)分大小寫的,但是,作為觀眾的我們,是要區(qū)分1和L的l,所以說,這個值,閑的沒事不要亂動,不然一個版本升級,舊數(shù)據(jù)就不兼容了,你還不知道問題在哪

如果某個序列化類的成員變量是對象類型,則該對象類型的類必須實現(xiàn)序列化

當(dāng)屬性是對象的時候,沒實現(xiàn)序列化接口,會產(chǎn)生異常Exception in thread “main” java.io.NotSerializableException: com....

代碼示例

給Student類添加一個Teacher類型的成員變量,其中Teacher是沒有實現(xiàn)序列化接口的

public class Student implements Serializable {
    private Integer age;
    private String name;
    private Teacher teacher;
    ...
}

//Teacher 沒有實現(xiàn)
public class Teacher  {
	......
}

序列化運行,就報NotSerializableException異常啦

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:16)

其實這個可以在上小節(jié)的底層源碼分析找到答案,一個對象序列化過程,會循環(huán)調(diào)用它的Object類型字段,遞歸調(diào)用序列化的,也就是說,序列化Student類的時候,會對Teacher類進行序列化,但是對Teacher沒有實現(xiàn)序列化接口,因此拋出NotSerializableException異常。所以如果某個實例化類的成員變量是對象類型,則該對象類型的類必須實現(xiàn)序列化

從淺入深理解序列化和反序列化

子類實現(xiàn)了Serializable,父類沒有實現(xiàn)Serializable接口的話,父類不會被序列化。

子類Student實現(xiàn)了Serializable接口,父類User沒有實現(xiàn)Serializable接口

//父類實現(xiàn)了Serializable接口
public class Student  extends User implements Serializable {
    private Integer age;
    private String name;
}

//父類沒有實現(xiàn)Serializable接口
public class User {
    String userId;
}

Student student = new Student();
student.setAge(25);
student.setName("jayWei");
student.setUserId("1");

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
objectOutputStream.writeObject(student);
objectOutputStream.flush();
objectOutputStream.close();

//反序列化結(jié)果
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student1 = (Student) objectInputStream.readObject();
System.out.println(student1.getUserId());
//output
/**  * null */

從反序列化結(jié)果,可以發(fā)現(xiàn),父類屬性值丟失了。因此子類實現(xiàn)了Serializable接口,父類沒有實現(xiàn)Serializable接口的話,父類不會被序列化。

下面是摘自 jdk api 文檔里面關(guān)于接口 Serializable 的描述

類通過實現(xiàn) java.io.Serializable 接口以啟用其序列化功能。
未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。
可序列化類的所有子類型本身都是可序列化的。因為實現(xiàn)接口也是間接的等同于繼承。
序列化接口沒有方法或字段,僅用于標(biāo)識可序列化的語義。

關(guān)于 serialVersionUID 的描述

序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關(guān)聯(lián),該序列號在反序列化過程中用于驗證序列化對象的發(fā)送者和接收者是否為該對象加載了與序列化兼容的類。

如果接收者加載的該對象的類的 serialVersionUID 與對應(yīng)的發(fā)送者的類的版本號不同,則反序列化將會導(dǎo)致 InvalidClassException。

可序列化類可以通過聲明名為 “serialVersionUID” 的字段(該字段必須是靜態(tài) (static)、最終 (final) 的 long 型字段)顯式聲明其自己的 serialVersionUID:

如果可序列化類未顯式聲明 serialVersionUID,則序列化運行時將基于該類的各個方面計算該類的默認(rèn) serialVersionUID 值,如“Java? 對象序列化規(guī)范”中所述。

為什么要顯示聲明serialVersionUID 值

計算默認(rèn)的 serialVersionUID 對類的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實現(xiàn)的不同可能千差萬別,這樣在反序列化過程中可能會導(dǎo)致意外的 InvalidClassException。

因此,為保證 serialVersionUID 值跨不同 java 編譯器實現(xiàn)的一致性,序列化類必須聲明一個明確的 serialVersionUID 值

還強烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應(yīng)用于直接聲明類 – serialVersionUID 字段作為繼承成員沒有用處。

數(shù)組類不能聲明一個明確的 serialVersionUID,因此它們總是具有默認(rèn)的計算值,但是數(shù)組類沒有匹配 serialVersionUID 值的要求。

JSON序列化和JDK序列化區(qū)別

對于對象轉(zhuǎn)化成json字符串和json字符串轉(zhuǎn)化成對象,也是屬于序列化和反序列化的范疇,相對于JDK提供的序列化機制,各有各的優(yōu)缺點:

  • JDK序列化/反序列化:原生方法不依賴其他類庫、但是不能跨平臺使用、字節(jié)數(shù)較大
  • json序列化/反序列化:json字符串可讀性高、可跨平臺使用無語言限制、擴展性好、但是需要第三方類庫、字節(jié)數(shù)較大

想了解json的使用可以看這里(https://mp.weixin.qq.com/s/S4R21FXSUPzpwBUv3uE6Xg)

常見的序列化

JDK原生序列化(前面已講過如何使用)

序列化的實現(xiàn)是由ObjectOutputStream完成,反序列化由ObjectInputStream完成。

JDK的序列化過程

從淺入深理解序列化和反序列化

序列化過程就是在讀取對象數(shù)據(jù)的時候,不斷的加入一些特殊分隔符,這些特殊分隔符在反序列化中使用。

頭部數(shù)據(jù)用來聲明序列化協(xié)議、版本,用于高版本向后兼容
對象數(shù)據(jù)主要包括類名、簽名、屬性名、屬性類型及屬性值、開頭結(jié)尾數(shù)據(jù)
存在對象引用、繼承的情況下,遞歸遍歷寫對象邏輯
任何一種序列化框架核心思想就是設(shè)計一個序列化協(xié)議,將對象的類型、屬性類型、屬性值按照固定的格式寫到二進制字節(jié)流中來完成序列化,再按照固定格式一一讀取對象的類型、屬性類型、屬性值,通過這些信息重建對象,完成反序列化

JSON序列化

JSON是典型的key-value形式,沒有數(shù)據(jù)類型,是一種文本型序列化框架
基于HTTP的RPC通信框架會采用JSON格式,存在以下問題

JSON序列化的額外空間開銷比較大,對于大數(shù)據(jù)量服務(wù)這意味著巨大的內(nèi)存和磁盤開銷,所以選擇JSON的時候,數(shù)據(jù)量要小。
JSON沒有類型,像Java這種強類型語言,需要反射統(tǒng)一解決,性能不太好

Hessian

Hession是動態(tài)類型、二進制、緊湊的、可跨語言移植的一種序列化框架。

Hessian協(xié)議要比JDK、JSON更加緊湊,性能要比JDK、JSON序列化高效很多,而且生成的字節(jié)數(shù)也更小。

Student student = new Student();
student.setNo(101);
student.setName("HESSIAN");
//把student對象轉(zhuǎn)化為byte數(shù)組
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(student);
output.flushBuffer();
byte[] data = bos.toByteArray();
bos.close();

 //把剛才序列化出來的byte數(shù)組轉(zhuǎn)化為student對象
 ByteArrayInputStream bis = new ByteArrayInputStream(data);
 Hessian2Input input = new Hessian2Input(bis);
 Student deStudent = (Student) input.readObject();
 input.close();

 System.out.println(deStudent);

存在問題:主要集中在對Java一些常見類型不支持

  • linked系列:LinkedHashMap、LinkedHashSet不支持,可以通過CollectionDesric
  • Locale類:通過擴展ContextSerializerFactory類修復(fù)
  • Byte/Short反序列化為Integer
Protobuf

Google內(nèi)部的混合語言數(shù)據(jù)標(biāo)準(zhǔn),是一種輕便、高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)序列化,支持 Java、Python、C++、Go 等語言。

Protobuf 使用的時候需要定義 IDLProtobuf使用的時候需要定義IDL文件,使用不用語言的IDL編譯器,生成序列化工具類。

序列化體積比JSON、Hessian要小
IDL能清晰的描述語義,所以足以幫助并保證應(yīng)用程序之間的類型不會丟失,無需類似 XML 解析器;
序列化、反序列化速度很快,不需要反射獲取類型
消息格式升級和兼容性不錯,可以做到向后兼容。

// IDl 文件格式
synax = "proto3";
option java_package = "com.test";
option java_outer_classname = "StudentProtobuf";
message StudentMsg {
	int32no=1;
 	//姓名
 	string name = 2;
}



StudentProtobuf.StudentMsg.Builder builder = StudentProtobuf.StudentMsg.newBuilder();
builder.setNo(103);
builder.setName("protobuf");

//把student對象轉(zhuǎn)化為byte數(shù)組
StudentProtobuf.StudentMsg msg = builder.build(); 
byte[] data = msg.toByteArray();

//把剛才序列化出來的byte數(shù)組轉(zhuǎn)化為student對象
StudentProtobuf.StudentMsg deStudent = StudentProtobuf.StudentMsg.parseFrom(dat
System.out.println(deStudent);

Protobuf 非常高效,但是對于具有反射和動態(tài)能力的語言來說,這樣用起來很費勁,這一點就不如 Hessian,比如用 Java 的話,這個預(yù)編譯過程不是必須的,可以考慮使用 Protostuff。

Protostuff

Protostuff 不需要依賴 IDL 文件,可以直接對 Java 領(lǐng)域?qū)ο筮M行反 / 序列化操作,在效率上跟 Protobuf 差不多,生成的二進制格式和 Protobuf 是完全相同的,可以說是一個 Java 版本的 Protobuf 序列化框架。但是

不支持null
不支持淡村的Map、List集合對象,需要包在對象里。

序列化底層

Serializable底層

Serializable接口,只是一個空的接口,沒有方法或字段,為什么這么神奇,實現(xiàn)了它就可以讓對象序列化了?

public interface Serializable {
}

為了驗證Serializable的作用,把實現(xiàn)序列化的對象,去掉實現(xiàn)Serializable接口,看序列化過程怎樣吧~

public class Student /**implements Serializable*/ {
    private Integer age;
    private String name;
    public Integer getAge() {
        return age;
    }    public void setAge(Integer age) {
        this.age = age;
    }    public String getName() {
        return name;
    }    public void setName(String name) {
        this.name = name;
    }
}

序列化過程中拋出異常啦,堆棧信息如下:

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:13)

順著堆棧信息看一下,原來有重大發(fā)現(xiàn),如下~

從淺入深理解序列化和反序列化

**原來底層是這樣:**ObjectOutputStream 在序列化的時候,會判斷被序列化的Object是哪一種類型,String?array?enum?還是 Serializable,如果都不是的話,拋出 NotSerializableException異常。所以呀,Serializable真的只是一個標(biāo)志,一個序列化標(biāo)志~

writeObject(Object)

序列化的方法就是writeObject,基于以上的demo,我們來分析一波它的核心方法調(diào)用鏈吧~(建議大家也去debug看一下這個方法,感興趣的話)

從淺入深理解序列化和反序列化

writeObject直接調(diào)用的就是writeObject0()方法,

public final void writeObject(Object obj) throws IOException {
	.....
    writeObject0(obj, false);
    .....
}

writeObject0 主要實現(xiàn)是對象的不同類型,調(diào)用不同的方法寫入序列化數(shù)據(jù),這里面如果對象實現(xiàn)了Serializable接口,就調(diào)用writeOrdinaryObject()方法~

private void writeObject0(Object obj, boolean unshared)
        throws IOException    {
    //String類型
    if (obj instanceof String) {
        //數(shù)組類型
        writeString((String) obj, unshared);
    } else if (cl.isArray()) {
        //枚舉類型
        writeArray(obj, desc, unshared);
    } else if (obj instanceof Enum) {
        //Serializable實現(xiàn)序列化接口
        writeEnum((Enum<?>) obj, desc, unshared);
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);    } else{
        //其他情況會拋異常~
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }    
    }
    .....
}       

writeOrdinaryObject()會先調(diào)用writeClassDesc(desc),寫入該類的生成信息,然后調(diào)用writeSerialData方法,寫入序列化數(shù)據(jù)

private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared) throws IOException {
    .....
    // 調(diào)用ObjectStreamClass的寫入方法
	writeClassDesc(desc, false);
    // 判斷是否實現(xiàn)了Externalizable接口
    if (desc.isExternalizable() && !desc.isProxy()) {
        writeExternalData((Externalizable) obj);
    } else {
        //寫入序列化數(shù)據(jù)
        writeSerialData(obj, desc);
    }
    .....
}

writeSerialData()實現(xiàn)的就是寫入被序列化對象的字段數(shù)據(jù)

private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
    for (int i = 0; i < slots.length; i++) {
        if (slotDesc.hasWriteObjectMethod()) {
            //如果被序列化的對象自定義實現(xiàn)了writeObject()方法,則執(zhí)行這個代碼塊
            slotDesc.invokeWriteObject(obj, this);
        } else {
            // 調(diào)用默認(rèn)的方法寫入實例數(shù)據(jù)
            defaultWriteFields(obj, slotDesc);
        }
    }
}

defaultWriteFields()方法,獲取類的基本數(shù)據(jù)類型數(shù)據(jù),直接寫入底層字節(jié)容器;

獲取類的obj類型數(shù)據(jù),循環(huán)遞歸調(diào)用writeObject0()方法,寫入數(shù)據(jù)~

private void defaultWriteFields(Object obj, ObjectStreamClass desc) throws IOException {
    // 獲取類的基本數(shù)據(jù)類型數(shù)據(jù),保存到primVals字節(jié)數(shù)組
    desc.getPrimFieldValues(obj, primVals);
    //primVals的基本類型數(shù)據(jù)寫到底層字節(jié)容器
    bout.write(primVals, 0, primDataSize, false);
    // 獲取對應(yīng)類的所有字段對象
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    // 獲取類的obj類型數(shù)據(jù),保存到objVals字節(jié)數(shù)組
    desc.getObjFieldValues(obj, objVals);
    //對所有Object類型的字段,循環(huán)
    for (int i = 0; i < objVals.length; i++) {
        //遞歸調(diào)用writeObject0()方法,寫入對應(yīng)的數(shù)據(jù)
        writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
        ......
    }
}

序列化注意事項

  • static靜態(tài)變量和transient 修飾的字段是不會被序列化的
  • serialVersionUID問題
  • 如果某個序列化類的成員變量是對象類型,則該對象類型的類必須實現(xiàn)序列化
  • 子類實現(xiàn)了序列化,父類沒有實現(xiàn)序列化,父類中的字段丟失問題

為什么static靜態(tài)變量和transient 修飾的字段是不會被序列化的

static靜態(tài)變量和transient 修飾的字段是不會被序列化的,我們來看例子分析一波~ Student類加了一個類變量gender和一個transient修飾的字段specialty

public class Student implements Serializable {
    private Integer age;
    private String name;
    public static String gender = "男";
    transient  String specialty = "計算機專業(yè)";
    public String getSpecialty() {
        return specialty;
    }
    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }
    
    @Override
    public String toString() {
        return "Student{" +"age=" + age + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", specialty='" + specialty + '\'' + '}';
    }
    ......
}

打印學(xué)生對象,序列化到文件,接著修改靜態(tài)變量的值,再反序列化,輸出反序列化后的對象~

從淺入深理解序列化和反序列化

運行結(jié)果:

序列化前Student{age=25, name='jayWei', gender='男', specialty='計算機專業(yè)'}
序列化后Student{age=25, name='jayWei', gender='女', specialty='null'}

對比結(jié)果可以發(fā)現(xiàn):

  • 1)序列化前的靜態(tài)變量性別明明是‘男’,序列化后再在程序中修改,反序列化后卻變成‘女’了,what?顯然這個靜態(tài)屬性并沒有進行序列化。其實,靜態(tài)(static)成員變量是屬于類級別的,而序列化是針對對象的~所以不能序列化哦
  • 2)經(jīng)過序列化和反序列化過程后,specialty字段變量值由’計算機專業(yè)’變?yōu)榭樟?,為什么呢?其實是因為transient關(guān)鍵字,它可以阻止修飾的字段被序列化到文件中,在被反序列化后,transient 字段的值被設(shè)為初始值,比如int型的值會被設(shè)置為 0,對象型初始值會被設(shè)置為null。

即:序列化并不保存靜態(tài)變量,Transient 關(guān)鍵字阻止該變量被序列化到文件中

序列化的底層是怎么實現(xiàn)的?

上文中已描述底層實現(xiàn),回答Serializable關(guān)鍵字作用,序列化標(biāo)志啦,源碼中,它的作用啦還有,可以回答writeObject幾個核心方法,如直接寫入基本類型,獲取obj類型數(shù)據(jù),循環(huán)遞歸寫入

序列化時,如何讓某些成員不要序列化?

可以用transient關(guān)鍵字修飾,它可以阻止修飾的字段被序列化到文件中,在被反序列化后,transient 字段的值被設(shè)為初始值,比如int型的值會被設(shè)置為 0,對象型初始值會被設(shè)置為null。

在 Java 中,Serializable 和 Externalizable 有什么區(qū)別

Externalizable繼承了Serializable,給我們提供 writeExternal() 和 readExternal() 方法, 讓我們可以控制 Java的序列化機制, 不依賴于Java的默認(rèn)序列化。正確實現(xiàn) Externalizable 接口可以顯著提高應(yīng)用程序的性能。

是否可以自定義序列化過程, 或者是否可以覆蓋 Java 中的默認(rèn)序列化過程?

可以的。我們都知道,對于序列化一個對象需調(diào)用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 讀取對象, 但 Java 虛擬機為你提供的還有一件事, 是定義這兩個方法。如果在類中定義這兩種方法, 則 JVM 將調(diào)用這兩種方法, 而不是應(yīng)用默認(rèn)序列化機制。同時,可以聲明這些方法為私有方法,以避免被繼承、重寫或重載。

在 Java 序列化期間,哪些變量未序列化?

static靜態(tài)變量和transient 修飾的字段是不會被序列化的。

靜態(tài)(static)成員變量是屬于類級別的,而序列化是針對對象的。

transient關(guān)鍵字修字段飾,可以阻止該字段被序列化到文件中。

動態(tài)代理是什么?有哪些應(yīng)用?

動態(tài)代理是運行時動態(tài)生成代理類。

動態(tài)代理的應(yīng)用有 Spring AOP數(shù)據(jù)查詢、測試框架的后端 mock、rpc,Java注解對象獲取???

怎么實現(xiàn)動態(tài)代理?

JDK 原生動態(tài)代理和 cglib 動態(tài)代理。

JDK 原生動態(tài)代理是基于接口實現(xiàn)的,而 cglib 是基于繼承當(dāng)前類的子類實現(xiàn)的。

11、靜態(tài)編譯和動態(tài)編譯

**靜態(tài)編譯:**在編譯時確定類型,綁定對象

**動態(tài)編譯:**運行時確定類型,綁定對象

如何選擇序列化方式

  • RPC序列化框架的性能和效率
  • 空間開銷:序列化之后的二進制數(shù)據(jù)的體積大小
  • 序列化協(xié)議的通用性和兼容性:多個語言,多個版本
  • 序列化協(xié)議的安全性:JDK 原生序列化存在安全漏洞

從淺入深理解序列化和反序列化

首選的還是 Hessian 與 Protobuf,因為他們在性能、時間開銷、空間開銷、通用性、 兼容性和安全性上,都滿足了我們的要求。

Hessian 在使用上更加方便,在對象的兼 容性上更好;
Protobuf 則更加高效,通用性上更有優(yōu)勢。

RPC使用過程中注意哪些問題

對象要盡量簡單,沒有太多的依賴關(guān)系
入?yún)⒑头祷刂刁w積不要太大
盡量使用簡單的、常用的原生對象。
對象不要有復(fù)雜的繼承關(guān)系。

對象序列化網(wǎng)絡(luò)傳輸案例

來自于博文:(47條消息) JAVA 通過網(wǎng)絡(luò)傳輸對象(對象序列化)簡單示例_wjwisme的博客-CSDN博客

首先,要傳輸?shù)腟tudent類(實現(xiàn)Serializable接口):
public class Student implements Serializable{
    private static final long serialVersionUID = 8683452581334592189L;
    private String name;
    private int age;
    private int score;
    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 int getScore() {
		return score;
	}
	public void setScore(int score) {
		this.score = score;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "name:" + name + " age:" + age + " score:" + score;
	}
}
Socket服務(wù)器類核心方法:
public static void openObjectServer(){
	ServerSocket ss = null;
	try {
		ss = new ServerSocket(1111);
		while(true){
            final Socket socket = ss.accept();
            new Runnable(){
                public void run() {
                    try { 
                        InputStream is = socket.getInputStream();
                        OutputStream os = socket.getOutputStream();
                        os.write("歡迎連接 服務(wù)器 一號!".getBytes());
				
                        ObjectInputStream ois = new ObjectInputStream(is);
                        Object object = ois.readObject();
                        
                        //打印對象
                        System.out.println(object);	
                        
                        //關(guān)閉socket
                        socket.close();
                    
                    }catch(Exception e){
                        e.printStackTrace();
                    
                    }finally{
                        if(socket != null )
						try {
							socket.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
                    }
                }
            }.run();
		}
	
    } catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	
    }finally{
		System.out.println("服務(wù)器關(guān)閉連接!");
		try {
			if(ss != null)
			ss.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

說明:該服務(wù)器方法可以接受多個客戶端連接,一般網(wǎng)絡(luò)服務(wù)器編程核心思想應(yīng)該都是這樣,開啟后,支持多線程處理多個連接請求,互不影響。

Socket客戶端類:
public class ClientSocketClass {
    public static void main(String[] args){
        Socket socket = null;
        try{
			socket = new Socket(InetAddress.getByName("127.0.0.1"),1111);
			OutputStream os = socket.getOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(os);
	
			Student student = new Student();
			student.setAge(20);
			student.setName("wjw");
			student.setScore(100);
	
			oos.writeObject(student);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
            try {
				if(socket != null)
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Java使用序列化實現(xiàn)深克隆

深拷貝的兩種實現(xiàn)方式
  1. 實現(xiàn)Cloneable接口,重寫Object類中clone()方法,實現(xiàn)層層克隆的方法。(clone()方法要求目標(biāo)類及其成員變量類都需要實現(xiàn)java.lang.Cloneable接口,并且覆寫java.lang.Objectclone()方法。
  2. 通過序列化(Serializable)的方法,將對象寫到流里,然后再從流中讀取出來。雖然這種方法效率很低,但是這種方法才是真正意義上的深度克隆。(序列化方法通過靜態(tài)方法實現(xiàn),其目標(biāo)類及其成員變量類都需要實現(xiàn)java.lang.Serializable接口

性能對比:序列化克隆和Cloneable,Cloneable更快

Java實現(xiàn)深克隆的兩種方式 - luankun0214 - 博客園 (cnblogs.com)

由于序列化的方式實現(xiàn)深度克隆性能較差,還是推薦使用Cloneable接口的方式重寫clone方法,但是注意該方法需要注意克隆對象的對象(多層)。

使用Serializable方式實現(xiàn)深拷貝

需要被克隆的對象實現(xiàn)Serializable接口,克隆的過程相對比較通用

被克隆對象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SerialCar implements Serializable {
	private static final long serialVersionUID = -7308342867043888945L;
	private String carType;
	private String carName;
 
    @Override
    public String toString() {
        return "SerialCar{" +
                "carType='" + carType + '\'' +
                ", carName='" + carName + '\'' +
                '}';
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SerialUser implements Serializable {
	private static final long serialVersionUID = -2167957013938386204L;
	private String username;
	private SerialCar serialCar;
    
    @Override
    public String toString() {
        return "SerialUser{" +
                "username='" + username + '\'' +
                ", serialCar=" + serialCar +
                '}';
    }
}
import java.io.*;

public class TestDeepClone {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SerialUser serialUser = new SerialUser("李四", new SerialCar("奔馳", "250"));
        //在內(nèi)存中開辟一塊緩沖區(qū),將對象序列化成流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
    	ObjectOutputStream oos = new ObjectOutputStream(bos);
    	oos.writeObject(serialUser);
        
		//找到這一塊緩沖區(qū),將字節(jié)流反序列化成另一個對象
    	ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    	ObjectInputStream ois = new ObjectInputStream(bis);
    	SerialUser cloneUser = (SerialUser)ois.readObject();

		SerialCar cloneCar = cloneUser.getSerialCar();
		cloneCar.setCarType("奧迪");
		cloneCar.setCarName("A8");
 
	    System.out.println(serialUser);
	    System.out.println(cloneUser);
	}
}
  1. 類需要實現(xiàn)java.lang.Serializable接口。否則代碼在運行時報錯。

解釋:
對象類Exam需要實現(xiàn)java.lang.Serializable接口,否則會在代碼執(zhí)行到os.writeObject(exam)時拋出NotSerializableException異常。

  1. 父類中的成員變量子類也需要實現(xiàn)java.lang.Serializable接口。否則在運行時報錯。

解釋:
當(dāng)父類中包含了成員變量子類時,如果只有父類實現(xiàn)java.lang.Serializable接口,但是子類沒有實現(xiàn)java.lang.Serializable接口,那么代碼執(zhí)行到os.writeObject(exam)時還是會**拋出NotSerializableException異常。

使用clone方式(淺拷貝)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Exam implements Cloneable {

    private int examId;
    private String examName;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

測試類

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Exam exam = new Exam(1, "語文考試");
        Exam cloneExam = (Exam) exam.clone();
        System.out.println(cloneExam != exam);
        System.out.println(cloneExam.equals(exam));
    }
}
控制臺輸出:
true
false

我們確實拷貝出了另一個對象。equals沒有覆寫,所以調(diào)用的是java.lang.Object中的以下方法:

public boolean equals(Object obj) {
	return (this == obj);
}
調(diào)用clone方法的前提
  1. Exam需要繼承java.lang.Cloneable接口。否則代碼在運行時報錯。

解釋:
調(diào)用exam.clone()的對象類Exam需要繼承Cloneable接口,否則會在代碼運行時拋出CloneNotSupportedException異常

  1. Exam需要覆寫父類的clone()方法。否則代碼在編譯時報錯。

解釋:
因為clone()java.lang.Object中是protected訪問控制。如果不覆寫,exam.clone()這句代碼無法編譯通過。

clone方法的存在問題

閱讀java.lang.Object中的clone()方法上的英文注釋時有這樣一段話:

this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation.

翻譯為:
該方法創(chuàng)建該對象類的新實例,并使用該對象相應(yīng)字段的內(nèi)容完全初始化其所有字段,就像通過賦值一樣; 字段的內(nèi)容本身不會被克隆。 因此,此方法執(zhí)行此對象的“淺復(fù)制”,而不是“深復(fù)制”操作。

代碼示例

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {
    private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Exam implements Cloneable {

    private int examId;
    private String examName;
    private Teacher teacher;

    @Override
    public String toString() {
        return "Exam{" +
                "examId=" + examId +
                ", examName='" + examName + '\'' +
                ", teacher=" + teacher +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

測試類

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Exam exam = new Exam(1, "語文考試");
        Teacher teacher = new Teacher("馬老師");
        exam.setTeacher(teacher);
        Exam cloneExam = (Exam) exam.clone();
        System.out.println(cloneExam != exam);
        System.out.println(cloneExam.equals(exam));

        cloneExam.getTeacher().setName("Lily");
        System.out.println(exam.toString());
        System.out.println(cloneExam.toString());
    }
}
public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Exam exam = new Exam(1, "語文考試");
        Exam cloneExam = (Exam) exam.clone();
        System.out.println(cloneExam != exam);
        System.out.println(cloneExam.equals(exam));
    }
}
控制臺輸出:
true
false
Exam{examId=1, examName='語文考試', teacher=Teacher{name='Lily'}}
Exam{examId=1, examName='語文考試', teacher=Teacher{name='Lily'}}

原本只想將克隆出來的考試的監(jiān)考老師改為 Lily ,但是把原考試對象的監(jiān)考老師也修改了

使用clone方式實現(xiàn)“深拷貝”
覆寫考試類Exam.javaclone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
	Exam exam = (Exam) super.clone();
	if (teacher != null) {
		Teacher teacher = (Teacher) this.teacher.clone();
		exam.setTeacher(teacher);
	}
	return exam;
}
解析

用上述方法,取代return super.clone()的默認(rèn)實現(xiàn)。同時因為這里調(diào)用了teacher.clone(),所以類Teacher也要實現(xiàn)Cloneable接口,覆寫clone()方法。

改寫老師類Teacher.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher implements Cloneable{

    private String name;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

控制臺輸出:
true
false
Exam{examId=1, examName=‘語文考試’, teacher=Teacher{name=‘馬老師’}}
Exam{examId=1, examName=‘語文考試’, teacher=Teacher{name=‘Lily’}}

使用泛型實現(xiàn)序列化深拷貝方法
public class Util {
    private Util() {}
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T deepCopy(T obj) {
        T cloneObj = null;
        try {
            //寫入字節(jié)流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配內(nèi)存,寫入原始對象,生成新對象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            
            //返回生成的新對象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

使用該方法可以在代碼編譯期檢查出沒有實現(xiàn)java.lang.Serializable接口的對象。

作者留言

平時也經(jīng)常用到序列化,使用無非就是 implements Serializable ,又加了個UID而已.一直沒有去深入理解其含義,為了把JAVA基礎(chǔ)打牢,對這些知識點都做了深度學(xué)習(xí),看了很多篇博文,存檔在這只是為了個人學(xué)習(xí)筆記使用,可能還有些文獻沒有標(biāo)進來,實在是太多了,記不住看過哪篇,還請見諒!!!

參考文獻

(47條消息) 對象在網(wǎng)絡(luò)中如何傳輸之序列化_序列化和傳輸方式有關(guān)嗎_程序員面試那點事兒的博客-CSDN博客

(47條消息) 靜態(tài)變量能被序列化嗎?_靜態(tài)變量可以序列化嗎_碼上得天下的博客-CSDN博客

一文搞懂序列化與反序列化 - 知乎 (zhihu.com)

全方位解析Java的序列化 - 知乎 (zhihu.com)

一步步分析Java深拷貝的兩種方式-clone和序列化 - 極客子羽 - 博客園 (cnblogs.com)文章來源地址http://www.zghlxwxcb.cn/news/detail-458604.html

到了這里,關(guān)于從淺入深理解序列化和反序列化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • C生萬物 | 從淺入深理解指針【最后部分】

    C生萬物 | 從淺入深理解指針【最后部分】

    我們前面學(xué)了四個部分了,如果沒有看前面的建議可以看一下前面的~~ C生萬物 | 從淺入深理解指針【第一部分】 C生萬物 | 從淺入深理解指針【第二部分】 C生萬物 | 從淺入深理解指針【第三部分】(轉(zhuǎn)移表的實現(xiàn)) C生萬物 | 從淺入深理解指針【第四部分】(qsort的使用和模

    2024年02月04日
    瀏覽(26)
  • C生萬物 | 從淺入深理解指針【第一部分】

    C生萬物 | 從淺入深理解指針【第一部分】

    1.1 內(nèi)存 在講內(nèi)存和地址之前,我們想有個生活中的案例: 假設(shè)有一棟宿舍樓,把你放在樓里,樓上有100個房間,但是房間沒有編號,你的一個朋友來找你玩,如果想找到你,就得挨個房子去找,這樣效率很低,但是我們?nèi)绻鶕?jù)樓層和樓層的房間的情況,給每個房間編上號

    2024年02月08日
    瀏覽(24)
  • this 之謎揭底:從淺入深理解 JavaScript 中的 this 關(guān)鍵字(二)

    系列首發(fā)于公眾號『前端進階圈』 ,若不想錯過更多精彩內(nèi)容,請“星標(biāo)”一下,敬請關(guān)注公眾號最新消息。 調(diào)用位置 在理解 this 的綁定過程之前,首先要理解 調(diào)用位置 : 調(diào)用位置就是函數(shù)在代碼中被調(diào)用的位置(而不是聲明的位置) 。 通常來說,尋找調(diào)用位置就是尋找

    2024年02月08日
    瀏覽(23)
  • 【Linux】序列化和反序列化

    【Linux】序列化和反序列化

    在網(wǎng)絡(luò)編程中,直接使用 結(jié)構(gòu)體 進行數(shù)據(jù)傳輸會出錯,因為 本質(zhì)上socket無法傳輸結(jié)構(gòu)體 ,我們只有將結(jié)構(gòu)體裝換為字節(jié)數(shù)組,或者是字符串格式來傳輸,然后對端主機收到了數(shù)據(jù),再將其轉(zhuǎn)化為結(jié)構(gòu)體,這就是序列化和反序列化的過程! 序列化 (Serialization)是將對象的狀態(tài)

    2024年02月10日
    瀏覽(19)
  • Unity-序列化和反序列化

    序列化是指把對象轉(zhuǎn)換為字節(jié)序列的過程,而反序列化是指把字節(jié)序列恢復(fù)為對象的過程。序列化最主要的用途就是傳遞對象和保存對象。 在Unity中保存和加載、prefab、scene、Inspector窗口、實例化預(yù)制體等都使用了序列化與反序列化。 1 自定義的具有Serializable特性的非抽象、

    2024年01月24日
    瀏覽(27)
  • Java序列化和反序列化

    目錄 一、序列化和反序列化 二、Java序列化演示 三、反序列化漏洞 1、含義 ?序列化就是內(nèi)存中的對象寫入到IO流中,保存的格式可以是二進制或者文本內(nèi)容。反序列化就是IO流還原成對象。 2、用途 (1)傳輸網(wǎng)絡(luò)對象 (2)保存Session 1、序列化 java.io.ObjectOutputStream代表對象

    2023年04月25日
    瀏覽(24)
  • Java序列化和反序列化機制

    在閱讀 ArrayList 源碼的時候,注意到,其內(nèi)部的成員變量動態(tài)數(shù)組 elementData 被Java中的 transient 修飾 transient 意味著Java在序列化時會跳過該字段(不序列化該字段) 而Java在默認(rèn)情況下會序列化類(實現(xiàn)了 Java.io.Serializable 接口的類)的所有非瞬態(tài)(未被 transient 修飾

    2024年03月15日
    瀏覽(27)
  • 簡單理解什么是序列化

    簡單理解什么是序列化

    序列化的目的就是為了對象可以在網(wǎng)絡(luò)層進行傳輸, 比如通過后端傳給前端數(shù)據(jù)。 我們以Java為例。 序列化就是把對象轉(zhuǎn)化為可傳輸?shù)淖止?jié)序列過程,這個字節(jié)序列可以是字符串,比如JSON格式的字符串,把內(nèi)存中的java對象轉(zhuǎn)化成JSON格式的字符串的過程,就是序列化的過程。

    2024年02月02日
    瀏覽(16)
  • [計算機網(wǎng)絡(luò)]---序列化和反序列化

    [計算機網(wǎng)絡(luò)]---序列化和反序列化

    前言 作者 :小蝸牛向前沖 名言 :我可以接受失敗,但我不能接受放棄 ?? 如果覺的博主的文章還不錯的話,還請 點贊,收藏,關(guān)注??支持博主。如果發(fā)現(xiàn)有問題的地方歡迎?大家在評論區(qū)指正? 目錄 ?一、再談協(xié)議 二、序列化和反序化 1、網(wǎng)絡(luò)版本計算器的場景搭建 2、

    2024年02月20日
    瀏覽(21)
  • jackjson自定義序列化和反序列化

    jackjson自定義序列化和反序列化

    JRT引用的jackjson作為json處理庫。由于JRT.ORM要求表不用datetime類型,把日期和時間用Int存儲,所以O(shè)RM要支持日期時間的轉(zhuǎn)換。為什么要把日期時間不用datetime而用Int,比如日期:20240117,時間就是從0點到當(dāng)前的秒數(shù)。因為不用datetime兼容性好,不會因為不同庫datetime函數(shù)不同而要

    2024年01月18日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包