Java序列化、反序列化

前言

??說(shuō)到序列化可能很多開(kāi)發(fā)人員對(duì)此并不清楚,甚至沒(méi)有了解,作為一個(gè)專(zhuān)業(yè)的程序員怎么能不懂這個(gè)呢,今天就來(lái)聊聊這個(gè),我們都知道,程序運(yùn)行時(shí)對(duì)象數(shù)據(jù)在內(nèi)存中,程序關(guān)閉后,數(shù)據(jù)就不存在了,這個(gè)時(shí)候我們希望對(duì)象數(shù)據(jù)能夠進(jìn)行持久保存,需要的時(shí)候根據(jù)那就拿出來(lái)用。那就用到了序列化,通過(guò)序列化我們可以將對(duì)象數(shù)據(jù)轉(zhuǎn)換為可取用的格式(比如二進(jìn)制、JSON)存儲(chǔ)到磁盤(pán),或者通過(guò)網(wǎng)絡(luò)傳輸?shù)搅硪粭l機(jī)器上,在需要的時(shí)候在還原到內(nèi)存中,這個(gè)過(guò)程叫反序列化。貼兩句專(zhuān)業(yè)描述:

序列化(serialization)在計(jì)算機(jī)科學(xué)的數(shù)據(jù)處理中,是指將數(shù)據(jù)結(jié)構(gòu)或?qū)ο鬆顟B(tài)轉(zhuǎn)換成可取用格式(例如存成文件,存于緩沖,或經(jīng)由網(wǎng)絡(luò)中發(fā)送),以留待后續(xù)在相同或另一臺(tái)計(jì)算機(jī)環(huán)境中,能恢復(fù)原先狀態(tài)的過(guò)程。

從一系列字節(jié)提取數(shù)據(jù)結(jié)構(gòu)的反向操作,是反序列化(也稱為解編組、deserialization、unmarshalling)。

知道了什么是序列化,那如何實(shí)現(xiàn)序列化和反序列化呢?

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

為了使對(duì)象可序列化,需實(shí)現(xiàn)java.io.Serializable接口或者Externalizable接口。

  • 對(duì)于使用Serializable聲明的類(lèi),對(duì)象序列化可以自動(dòng)保存和還原對(duì)象每個(gè)類(lèi)的字段,并通過(guò)添加字段或超類(lèi)型來(lái)自動(dòng)處理不斷演變的類(lèi)。一個(gè)可序列化的類(lèi)可以聲明要保存或還原其哪些字段,以及寫(xiě)入和讀取可選值和對(duì)象。
  • 對(duì)于使用Externalizable聲明的類(lèi),對(duì)象序列化將完全控制其外部格式以及如何保存和還原超類(lèi)型的狀態(tài)委托給該類(lèi)。

實(shí)現(xiàn)接口是不夠的,我們還需要通過(guò)對(duì)象流來(lái)對(duì)數(shù)據(jù)進(jìn)行讀取和寫(xiě)入:

ObjectOutputStream類(lèi)包含用于序列化對(duì)象的writeObject()方法。

public final void writeObject(Object obj)  throws IOException

ObjectInputStream類(lèi)包含用于反序列化對(duì)象的readObject()方法。

public final Object readObject()  throws IOException, ClassNotFoundException

??關(guān)于序列化屬性的要求,默認(rèn)可序列化屬性字段被聲明為非transient和非static字段。在可序列化的類(lèi)中聲明serialPersistentFields字段,可以覆蓋此默認(rèn)計(jì)算 serialPersistentFields。必須使用ObjectStreamField列出目標(biāo)對(duì)象和可序列化字段的類(lèi)型的對(duì)象數(shù)組來(lái)初始化該字段。字段的修飾符必須是private,static的和final的,如果該字段的值為null或著不是此實(shí)例的ObjectStreamField[],或不存在privatestatic,final修飾符,那么此定義沒(méi)有任何作用。

serialVersionUID

??serialVersionUID 是 Java 為每個(gè)序列化類(lèi)產(chǎn)生的版本標(biāo)識(shí),可用來(lái)保證在反序列時(shí),發(fā)送方發(fā)送的和接受方接收的是可兼容的對(duì)象。如果接收方接收的類(lèi)的 serialVersionUID 與發(fā)送方發(fā)送的serialVersionUID不一致,進(jìn)行反序列時(shí)會(huì)拋出 InvalidClassException
??如果序列化的類(lèi)未明確聲明serialVersionUID,則序列化運(yùn)行時(shí)將根據(jù)該類(lèi)的各個(gè)方面為該類(lèi)計(jì)算一個(gè)默認(rèn)值。那么重新編譯后的serialVersionUID將會(huì)發(fā)生變化,任何地方的更改都會(huì)影響到序列化的數(shù)據(jù),因此建議所有的序列化類(lèi)顯式的聲明serialVersionUID值。
??建議對(duì)UID使用private修飾符,因?yàn)樗鳛槔^承屬性沒(méi)有用。

  • 如果父類(lèi)已經(jīng)實(shí)現(xiàn)了Serializable接口,則子類(lèi)不需要實(shí)現(xiàn)它,反之亦然。
  • 僅非靜態(tài)屬性通過(guò)序列化過(guò)程可以保存。
  • 靜態(tài)屬性和臨時(shí)屬性不會(huì)通過(guò)序列化過(guò)程保存。因此,如果不想保存非靜態(tài)數(shù)據(jù)屬性值,需要加入transient聲明。
  • 反序列化對(duì)象時(shí),永遠(yuǎn)不會(huì)調(diào)用對(duì)象的構(gòu)造函數(shù)。
  • 關(guān)聯(lián)對(duì)象必須實(shí)現(xiàn)Serializable接口。

用例

定義User類(lèi)


import lombok.Data;
import java.io.Serializable;

/**
 * User
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 11:42
 */
@Data
public class User implements Serializable {
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 4760229593345099240L;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年齡
     */
    private Integer age;
    /**
     * 住址
     */
    private transient String address;

    public User(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

原生方式
序列化對(duì)象
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.logging.Logger;

/**
 * 序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 11:43
 */
public class Serialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File("user.txt");
        User user = new User("小美", 18);
        //將對(duì)象保存在文件中
        FileOutputStream fileOut = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        //對(duì)象序列化的方法
        out.writeObject(user);
        //關(guān)閉
        out.close();
        fileOut.close();
        LOGGER.info("對(duì)象已被序列化");
    }
}

運(yùn)行程序,在當(dāng)前項(xiàng)目下找到user.txt文件,打開(kāi)查看,即可看到序列化后的內(nèi)容。

反序列化
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.logging.Logger;

/**
 * 反序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 15:21
 */
public class Deserialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();


    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //從文件中讀取對(duì)象
        File file = new File("user.txt");
        FileInputStream fileInput = new FileInputStream(file);
        ObjectInputStream in = new ObjectInputStream(fileInput);
        //對(duì)象反序列化的方法
        User userRead = (User) in.readObject();
        //關(guān)閉
        fileInput.close();
        in.close();
        LOGGER.info("對(duì)象已被反序列化" + " " + userRead.toString());
    }
}

控制臺(tái)輸出,可以看到將剛才對(duì)象中的數(shù)據(jù)反序列化了出來(lái),由于address屬性我們聲明了transient,序列化并沒(méi)有保存。

信息: 對(duì)象已被反序列化 User(name=小美, age=18, address=null)
serialPersistentFields

在上述基礎(chǔ)上,測(cè)試聲明serialPersistentFields情況。

  • 定義為null,序列化后在反序列化,打印出了對(duì)象字段數(shù)據(jù),無(wú)效
private static final ObjectStreamField[] serialPersistentFields = null;
  • 定義User類(lèi)不存在字段、序列化拋出java.io.InvalidClassException 異常
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("name_",String.class)};
  • 聲明為{}沒(méi)有序列化任何字段,序列化、序列化后進(jìn)行打印,對(duì)象字段數(shù)據(jù)為null,生效
private static final ObjectStreamField[] serialPersistentFields = {};
  • 只配置序列化name字段,序列化后反序列化打印,對(duì)象輸出只有name有值,生效。
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("name",String.class)};
JSON序列化

?? JSON(JavaScript Object Notation)是一種輕量級(jí)的數(shù)據(jù)交換格式。JSON序列化就是將數(shù)據(jù)對(duì)象轉(zhuǎn)為JSON字符串,JSON的可讀性更好,方便調(diào)試。廣泛使用的JSON庫(kù)有Jackson、fastjson。
?? JSON序列化通常會(huì)通過(guò)網(wǎng)絡(luò)傳輸對(duì)象、而對(duì)象中往往會(huì)存在敏感數(shù)據(jù),所以序列化常常成為黑客攻擊點(diǎn),攻擊者巧妙的利用反序列化過(guò)程構(gòu)造惡意代碼,是程序在反序列化過(guò)程中執(zhí)行任意代碼,Jacksonfastjson 都曾經(jīng)出現(xiàn)過(guò)反序列化漏洞,那么如何防范攻擊呢?有些敏感屬性不需要進(jìn)行序列化傳輸,可以加transient關(guān)鍵字聲明,避免此屬性序列化,如果一定要傳遞敏感數(shù)據(jù),可以使用對(duì)稱和非對(duì)稱加密傳輸,在進(jìn)行解密處理后還原到對(duì)象中。開(kāi)發(fā)者一定要有安全防范意識(shí),對(duì)傳入數(shù)據(jù)內(nèi)容進(jìn)行校驗(yàn)或權(quán)限控制,及時(shí)更新JSON庫(kù)安全漏洞,避免受到攻擊。在工程中目前自己常用的JSON庫(kù)是fastjson,fastjson可以通過(guò)定制序列化對(duì)需要序列化的數(shù)據(jù)進(jìn)行處理。fastjson定制序列化

序列化
import com.alibaba.fastjson.JSON;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Logger;

/**
 * JSON序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 17:55
 */
public class Serialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException {
        //創(chuàng)建對(duì)象
        User user = new User("小美", 18, "山東省濟(jì)南市");
        //序列化
        String json = JSON.toJSONString(user);
        //寫(xiě)入文件
        FileOutputStream fileOut = new FileOutputStream(new File("user.json"));
        fileOut.write(json.getBytes());
        LOGGER.info("序列化對(duì)象完成");
    }
}

打開(kāi)項(xiàng)目根目錄下的user.json,便可以看到對(duì)象序列化后的JSON串。

反序列化
import com.alibaba.fastjson.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Logger;

/**
 * JSON反序列化
 *
 * @author SanLi
 * Created by qinggang.zuo@gmail.com / 2689170096@qq.com on 2020/1/7 17:55
 */
public class Deserialization {
    /**
     * Logger
     */
    private static final Logger LOGGER = Logger.getAnonymousLogger();

    public static void main(String[] args) throws IOException {
        //讀取文件
        FileInputStream fileOut = new FileInputStream(new File("user.json"));
        int len;
        byte[] buf = new byte[1024];
        while ((len = fileOut.read(buf)) != -1) {
            //反序列化
            User user = JSONObject.parseObject(new String(buf, 0, len), User.class);
            LOGGER.info(user.toString());
        }
        LOGGER.info("反序列化對(duì)象完成");
    }
}

控制臺(tái)輸出打印的對(duì)象,由于address 屬性使用transient聲明,所以并沒(méi)有被序列化,所以也不會(huì)反序列化,所以是null

信息: User(name=小美, age=18, address=null)

JSON序列化應(yīng)用場(chǎng)景是很多的,其中在自己負(fù)責(zé)的項(xiàng)目里,強(qiáng)制規(guī)定了對(duì)象日志打印格式為JSON,因?yàn)槌霈F(xiàn)問(wèn)題非常方便查看并 Debug,包括spring data redis 序列化用的也是JSON方式。

參考

Java Serializable Doc
oracle serialization doc

最后編輯于
?著作權(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)容

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