Java里面的序列化和反序列化

(一)什么是序列化和反序列化

序列化和反序列化是將對(duì)象轉(zhuǎn)化成字節(jié)數(shù)組以方便保存或者用于網(wǎng)絡(luò)傳輸,這個(gè)對(duì)象可以是一個(gè)圖片,一個(gè)字符串,一個(gè)class等等,常見(jiàn)的序列化格式有字節(jié)數(shù)組,json格式,xml格式,更加高效的有g(shù)oogle開(kāi)源的Protocol Buffers,以及Apache Avro。

(二)為什么需要序列化和反序列化

(1)實(shí)現(xiàn)數(shù)據(jù)持久化,一般jvm的里面數(shù)據(jù),在java程序退出時(shí),所有的狀態(tài)都不會(huì)保留,通過(guò)序列化可以將需要的數(shù)據(jù)給持久化到磁盤文件或者數(shù)據(jù)庫(kù),這樣就可以在下次jvm啟動(dòng)的時(shí)候再把數(shù)據(jù)重新還原出來(lái)。

(2)利用序列化實(shí)現(xiàn)遠(yuǎn)程通信,即在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列,這種場(chǎng)景一般在socket或者rpc的服務(wù)中比較常見(jiàn)。

(三)Java里面如何實(shí)現(xiàn)序列化和反序列化

在java里面有兩種方式可以實(shí)現(xiàn)對(duì)象的序列化:

(1)實(shí)現(xiàn)Serializable接口的類,jdk會(huì)自動(dòng)幫我們序列化該類所有的信息, 但如果用戶定義了writeObject和readObject方法,那么在序列化和反序列化的時(shí)候會(huì)通過(guò)反射優(yōu)先調(diào)用自定義的方法

(2)實(shí)現(xiàn)Externalizable接口的類,需要用戶自定義序列化和反序列化的邏輯,分別重寫writeExternal和readExternal方法。

下面看一個(gè)例子,首先我們定義一個(gè)Person類并實(shí)現(xiàn)了序列化接口:

package cn.xby;

import java.io.Serializable;

public class Person implements Serializable {

private static final long serialVersionUID = 1L;
private transient String address;
private String name;
private int age;



@Override
public String toString() {
    return "Person [address=" + address + ", name=" + name + ", age=" + age + "]";
}
public String getAddress() {
    return address;
}
public void setAddress(String address) {
    this.address = address;
}
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 Person(String address, String name, int age) {
    super();
    this.address = address;
    this.name = name;
    this.age = age;
}
public Person() {
    super();
}

}

然后我們定義了幫助實(shí)現(xiàn)序列化和反序列化的工具類:

package cn.xby;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SerializeTools {

/**
 * 將任何實(shí)現(xiàn)了序列化接口的對(duì)象轉(zhuǎn)成字節(jié)數(shù)組
 * @param obj
 * @return
 * @throws Exception 
 */
public static byte[] toBytes(Serializable obj) throws Exception {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(obj);
    return byteArrayOutputStream.toByteArray();
    
}

/**
 * 將任何序列化的字節(jié)數(shù)組給還原成對(duì)象
 * @param bytes
 * @return
 * @throws Exception
 */
public static Object toObj(byte[] bytes) throws Exception {
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    Object obj = objectInputStream.readObject();
    return obj;
}

/**
 * 將一個(gè)實(shí)現(xiàn)了序列化的對(duì)象給序列化成文件
 * @param obj
 * @param storePath
 * @throws Exception
 */
public static void toFile (Serializable obj,String storePath) throws Exception {
    FileOutputStream fileOutputStream = new FileOutputStream(new File(storePath));
    @SuppressWarnings("resource")
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(obj);
    
}

/**
 * 將序列化對(duì)象還原成文件
 * @param storePath
 * @return
 * @throws Exception
 */
public static Object fromFile(String storePath) throws Exception {
    FileInputStream fileInputStream = new FileInputStream(new File(storePath));
    @SuppressWarnings("resource")
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    Object obj = objectInputStream.readObject();
    return obj;
    
    
}

}

然后看下我們的測(cè)試類,分別測(cè)試文件的序列化和字節(jié)的序列化

package cn.xby;

public class TestSerialize {

public static void main(String[] args) throws Exception {
    // TODO Auto-generated method stub
    Person person = new Person("北京海淀","張三",25);
    fileTest(person);//文件測(cè)試序列化和反序列化
    System.out.println("======================================");
    byteTest(person);//字節(jié)測(cè)試序列化和反序列化

}


public static void fileTest(Person p1) throws Exception {
    String storePath = "D://temp.out";
    System.out.println("基于文件序列化前:" + p1);
    SerializeTools.toFile(p1, storePath);
    Person p2 = (Person)SerializeTools.fromFile(storePath);
    System.out.println("基于文件序列化后:" + p2);
}

public static void byteTest(Person p1) throws Exception {
    System.out.println("基于字節(jié)序列化前:" + p1);
    byte[] bytes = SerializeTools.toBytes(p1);//序列化成字節(jié)數(shù)組    
    Person p2 = (Person)SerializeTools.toObj(bytes);//反序列化成對(duì)象
    System.out.println("基于字節(jié)反序列化后:" + p2);

}

}

運(yùn)行后輸出如下:

基于文件序列化前:Person [address=北京海淀, name=張三, age=25]
基于文件序列化后:Person [address=null, name=張三, age=25]
======================================
基于字節(jié)序列化前:Person [address=北京海淀, name=張三, age=25]
基于字節(jié)反序列化后:Person [address=null, name=張三, age=25]

細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)地址這個(gè)字段,在反序列化后字段值丟失了,這里說(shuō)明下:

(1)在java里面transient關(guān)鍵詞修飾的成員變量是不會(huì)被序列化的,這一點(diǎn)在上面的輸出中已經(jīng)得到驗(yàn)證了,注意transient關(guān)鍵詞只能修飾成員變量,不能修飾類和方法

(2)在java里面static關(guān)鍵詞修飾的字段也是不會(huì)被序列化的,因?yàn)檫@個(gè)是類的字段,而序列化是針對(duì)對(duì)象的。

引申一下:java的內(nèi)存分配有棧和堆以及永久代,棧中存放基本變量,數(shù)組和對(duì)象引用,堆中存放對(duì)象,當(dāng)有static修飾的變量或方法會(huì)被放到永久代里面。它先于對(duì)象而存在,不依賴實(shí)例,無(wú)論是變量,方法,還是代碼塊,只要用static修飾,就是在類被加載時(shí)就已經(jīng)準(zhǔn)備好了,也就是可以被使用或者已經(jīng)被執(zhí)行,都可以脫離對(duì)象而執(zhí)行,所以在類加載時(shí)靜態(tài)變量的值其實(shí)已經(jīng)還原出來(lái)了之后才是反序列化出來(lái)成員變量的值。

(3)在上面的Person類里面,相信大家還看到了一個(gè)用static final long修飾的 serialVersionUID字段,這個(gè)字段的功能是用來(lái)標(biāo)識(shí)類版本的兼容性:

舉個(gè)例子,假如現(xiàn)在沒(méi)有定義serialVersionUID這個(gè)字段,jdk默認(rèn)是根據(jù)類信息計(jì)算一個(gè)版本值,在類已經(jīng)被序列化成文件后,我們又修改了類結(jié)構(gòu),比如新增了幾個(gè)字段,這個(gè)時(shí)候拿著新版本的類去反序列化舊版本的類,就會(huì)拋出下面的異常:

意思就是版本不一致,導(dǎo)致失敗,如果我們定義這個(gè)值,并且新舊版本的值一樣,不管新增沒(méi)新增字段,都可以反序列化成功,默認(rèn)新增字段的值是jdk給成員變量初始化的值,比如字符串就是null。

(四)定制自己的序列化和反序列化方法

上面提到過(guò)實(shí)現(xiàn)了Serializable接口的類,我們可以重寫下面的方法來(lái)自定義序列化邏輯:

private void writeObject(ObjectOutputStream out) throws Exception {
    System.out.println("call write");
    out.writeObject(address);
    out.writeObject(name);
    out.writeInt(age);
    
}

private void readObject(ObjectInputStream in) throws Exception {
    System.out.println("call read");
    address = (String)in.readObject();
    name = (String)in.readObject();
    age = in.readInt();
}

再次執(zhí)行測(cè)試方法,輸出結(jié)果如下:

基于文件序列化前:Person [address=北京海淀, name=張三, age=25]
call write
call read
基于文件序列化后:Person [address=北京海淀, name=張三, age=25]
======================================
基于字節(jié)序列化前:Person [address=北京海淀, name=張三, age=25]
call write
call read
基于字節(jié)反序列化后:Person [address=北京海淀, name=張三, age=25]

這次我們發(fā)現(xiàn)了被transient修飾的address字段竟然也有值了,為什么?因?yàn)槲覀冏远x序列化的時(shí)候把地址也給序列化了,所以這個(gè)時(shí)候無(wú)論你用不用transient關(guān)鍵詞都無(wú)關(guān)緊要了。

注意如果實(shí)現(xiàn)了上面的方法其實(shí)和使用Externalizable就相差無(wú)幾了,所以在這里不再給出Externalizable的例子

(五)什么時(shí)候應(yīng)該readObject和writeObject

在effective java里面提到過(guò):

當(dāng)一個(gè)對(duì)象的物理表示方法與它的邏輯數(shù)據(jù)內(nèi)容有實(shí)質(zhì)性差別時(shí),使用默認(rèn)序列化形式有N種缺陷。

其實(shí)是建議我們重寫的,這樣可以更好的控制序列化的過(guò)程,如果能減少一些不必要的序列化的字段,其實(shí)對(duì)我們的程序性能也是一種提升。

總結(jié):

本文介紹了Java里面序列化和反序列化功能和使用以及一些注意事項(xiàng),序列化其實(shí)還提供了深度克隆的功能,尤其是當(dāng)類里面的引用層次比較多及引用特別復(fù)雜的時(shí)候,通過(guò)序列化來(lái)深度拷貝對(duì)象也是一種比較便利的方法,除此之外,我們還應(yīng)該知道序列化和反序列化和反射一樣,弱化了java安全權(quán)限修飾符的作用,無(wú)論你privte還是protected修飾的字段,在序列化和反射面前都是無(wú)作用的,所以一些敏感信息的序列化尤其是在網(wǎng)絡(luò)上傳輸?shù)娜缑艽a,金錢什么的,都應(yīng)該考慮加密或者其他安全措施。

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

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

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 11,201評(píng)論 0 24
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化,必須實(shí)現(xiàn)Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,555評(píng)論 1 3
  • 一、Java 簡(jiǎn)介 Java是由Sun Microsystems公司于1995年5月推出的Java面向?qū)ο蟪绦蛟O(shè)計(jì)...
    子非魚(yú)_t_閱讀 4,564評(píng)論 1 44
  • 我最近的‘寫作‘’表現(xiàn)真的無(wú)法堪稱寫作,無(wú) 論在選題上,標(biāo)題上,架構(gòu)上,排版上都 無(wú)突破或者毫無(wú)用心之處,那...
    王海嶺閱讀 231評(píng)論 0 0
  • “剛剛把孩子揍了一頓!明明可以考100分,卻都做錯(cuò)了!” “看到孩子拿回來(lái)的數(shù)學(xué)試卷,頓時(shí)火冒三丈!現(xiàn)在血壓一定賊...
    嚶說(shuō)親子閱讀 1,178評(píng)論 0 0

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