分布式架構(gòu)基礎(chǔ)-序列化和反序列化

1.序列化的意義
2.java原生序列化
3.serialVersionUID 的作用
4.靜態(tài)變量序列化
5.父類的序列化
6.Transient 關(guān)鍵字
7.繞開 transient 機(jī)制的辦法
8.序列化的存儲規(guī)則
9.序列化實現(xiàn)深克隆
10.分布式架構(gòu)下常見序列化技術(shù)

序列化的意義

java平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java對象犁河,但一般情況下已卷,只有當(dāng) JVM處于運行時色瘩,這些對象才可能存在挚歧,即這些對象的生命周期不會比JVM的生命周期更長五督。但在現(xiàn)實應(yīng)用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象介陶,并在將來重新讀取被保存的對象扇单,Java 對象序列化就能夠幫助我們實現(xiàn)該功能换可。
簡單來說椎椰,序列化是把對象的狀態(tài)信息轉(zhuǎn)化為可存儲或傳輸?shù)男问剑簿褪前褜ο筠D(zhuǎn)化為字節(jié)序列的過程稱為對象的序列化沾鳄。反序列化是序列化的逆向過程慨飘,把字節(jié)數(shù)組反序列化為對象,把字節(jié)序列恢復(fù)為對象的過程稱為對象的反序列化译荞。


image.png
java原生序列化

前面的代碼中演示了瓤的,如何通過 JDK 提供了 Java 對象的序列化方式實現(xiàn)對象序列化傳輸,主要通過輸出流java.io.ObjectOutputStream和對象輸入流java.io.ObjectInputStream來實現(xiàn)磁椒。

java.io.ObjectOutputStream:表示對象輸出流 , 它的 writeObject(Object obj)方法可以對參數(shù)指定的 obj 對象進(jìn)行序列化堤瘤,把得到的字節(jié)序列寫到一個目標(biāo)輸出流中玫芦。
java.io.ObjectInputStream:表示對象輸入流 ,它的 readObject()方法源輸入流中讀取字節(jié)序列浆熔,再把它們反序列化成為一個對象,并將其返回
需要注意的是桥帆,被序列化的對象需要實現(xiàn) java.io.Serializable 接口

例:基于 socket 進(jìn)行對象傳輸

public class User implements Serializable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class SocketServerProvider {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=null;
        BufferedReader in=null;
        try{
            serverSocket=new ServerSocket(8080);
            Socket socket=serverSocket.accept();
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            User user=(User)objectInputStream.readObject();
            System.out.println(user);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket!=null){
                serverSocket.close();
            }
        }
    }
}
public class SocketClientConsumer {
    public static void main(String[] args) {
        Socket socket=null;
        ObjectOutputStream out=null;
        try {
            socket=new Socket("127.0.0.1",8080);
            User user=new User();
            out=new ObjectOutputStream(socket.getOutputStream());
            out.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

serialVersionUID 的作用

Java的序列化機(jī)制是通過判斷類的serialVersionUID來驗證版本一致性的医增。在進(jìn)行反序列化時,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實體類的serialVersionUID進(jìn)行比較老虫,如果相同就認(rèn)為是一致的叶骨,可以進(jìn)行反序列化,否則就會出現(xiàn)序列化版本不一致的異常祈匙,即是InvalidCastException忽刽。
如果沒有為指定的class配置serialVersionUID,那么java編譯器會自動給這個class進(jìn)行一個摘要算法夺欲,類似于指紋算法跪帝,只要這個文件有任何改動,得到的UID就會截然不同些阅,可以保證在這么多類中伞剑,這個編號是唯一的。
serialVersionUID有兩種顯示的生成方式:
一是默認(rèn)的1L市埋,比如:private static final long serialVersionUID = 1L;
二是根據(jù)類名黎泣、接口名、成員方法及屬性等來生成一個64位的哈希字段缤谎。
當(dāng)實現(xiàn)java.io.Serializable接口的類沒有顯式地定義一個serialVersionUID變量的時候抒倚,Java序列化機(jī)制會根據(jù)編譯的Class自動生成一個serialVersionUID當(dāng)作序列化版本來比較用,這種情況下坷澡,如果Class文件(類名托呕,方法名等)沒有發(fā)生變化(增加空格,換行,增加注釋等等)镣陕,就算再編譯多次谴餐,serialVersionUID也不會變化。

靜態(tài)變量序列化

在User中添加一個全局的靜態(tài)變量num 呆抑, 在執(zhí)行序列化以后修改num的值為10岂嗓, 然后通過反序列化以后得到的對象去輸出num的值。

public class User implements Serializable{
    private String name;
    private int age;
    public static int num=5;//設(shè)置靜態(tài)變量num值為5
...
...
...
}

public class Test {

    public static void main(String[] args) {
        ISerializer serializer=new JavaSerializer();
        User user=new User();
        user.setName("taofut");
        user.setAge(27);
        byte[] bytes=serializer.serializer(user);
        //序列化以后鹊碍,將num值改為10
        User.num=10;

        User user1=serializer.deSerializer(bytes,User.class);
        System.out.println(user1+"--"+User.num);
    }
    //執(zhí)行結(jié)果:User{name='taofut', age=27}--10
}

最后的輸出是10厌殉,我們可能會覺得,10是在序列化之后修改的侈咕,按理說反序列化應(yīng)該輸出的是5才對公罕。理論上打印的num是從讀取的對象里獲得的,應(yīng)該是保存時的狀態(tài)才對耀销。之所以打印10的原因在于序列化時楼眷,并不保存靜態(tài)變量,這其實比較容易理解熊尉,序列化保存的是對象的狀態(tài)罐柳,靜態(tài)變量屬于類的狀態(tài),因此序列化并不保存靜態(tài)變量狰住。

父類的序列化

一個子類實現(xiàn)了Serializable接口张吉,而它的父類卻沒有實現(xiàn)Serializable接口,在子類中設(shè)置父類的成員變量的值催植,接著序列化該子類對象肮蛹。再反序列化出來以后輸出父類屬性的值。結(jié)果應(yīng)該是什么创南?

public class SuperUser {
    String sex;

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

public class Test {

    public static void main(String[] args) {
        ISerializer serializer=new JavaSerializer();
        User user=new User();
        user.setName("taofut");
        user.setAge(27);
        user.setSex("男");
        byte[] bytes=serializer.serializer(user);
        User.num=10;

        User user1=serializer.deSerializer(bytes,User.class);
        System.out.println(user1+"--"+user1.getSex());
    }
    //執(zhí)行結(jié)果:User{name='taofut', age=27}--null
}

最終結(jié)果發(fā)現(xiàn)伦忠,父類的sex字段的值為null,也就是說父類沒有實現(xiàn)序列化扰藕。
結(jié)論:
1》 當(dāng)一個父類沒有實現(xiàn)序列化時缓苛,子類繼承該父類并且實現(xiàn)了序列化。在反序列化該子類后邓深,是沒辦法獲取到父類的屬性值的未桥。
2》當(dāng)一個父類實現(xiàn)序列化,子類自動實現(xiàn)序列化芥备,不需要再顯示實現(xiàn)Serializable接口冬耿。
3》當(dāng)一個對象的實例變量引用了其他對象,序列化該對象時也會把引用對象進(jìn)行序列化萌壳,但是前提是該引用對象必須實現(xiàn)序列化接口亦镶。

Transient關(guān)鍵字

Transient關(guān)鍵字的作用是控制變量的序列化日月,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中缤骨,在被反序列化后爱咬,transient變量的值被設(shè)為初始值,如 int型的是0绊起,對象型的是null精拟。

public class UserTwo extends SuperUser implements Serializable{
    private String name;
    private int age;
    public static int num=5;

    private transient String address;
    ...
    ...
    ...
}

public class Test {

    public static void main(String[] args) {
        ISerializer serializer=new JavaSerializer();
        UserTwo user=new UserTwo();
        user.setName("taofut");
        user.setAge(27);
        user.setSex("男");
        user.setAddress("浙江省");//該字段被transient修飾過
        byte[] bytes=serializer.serializer(user);

        UserTwo user1=serializer.deSerializer(bytes,UserTwo.class);
        System.out.println(user1+"--"+user1.getAddress());
    }
    //執(zhí)行結(jié)果:User{name='taofut', age=27}--null
}
繞開transient機(jī)制的辦法
public class UserTwo extends SuperUser implements Serializable{
    private String name;
    private int age;
    public static int num=5;

    private transient String address;
    //序列化對象
    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
        objectOutputStream.defaultWriteObject();
        objectOutputStream.writeObject(address);
    }
    //反序列化
    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        objectInputStream.defaultReadObject();
        address=(String)objectInputStream.readObject();
    }
    ...
    ...
    ...
}

public class Test {

    public static void main(String[] args) {
        ISerializer serializer=new JavaSerializer();
        UserTwo user=new UserTwo();
        user.setName("taofut");
        user.setAge(27);
        user.setSex("男");
        user.setAddress("浙江省");
        byte[] bytes=serializer.serializer(user);


        UserTwo user1=serializer.deSerializer(bytes,UserTwo.class);
        System.out.println(user1+"--"+user1.getAddress());
    }
    //執(zhí)行結(jié)果:User{name='taofut', age=27}--浙江省
}

以上代碼可能會產(chǎn)生一個疑問:writeObject和readObject這兩個私有的方法,既不屬于 Object虱歪、也不是Serializable蜂绎,為什么能夠在序列化的時候被調(diào)用呢? 原因是笋鄙,ObjectOutputStream使用了反射來尋找是否聲明了這兩個方法师枣。因為ObjectOutputStream使用getPrivateMethod,(反射可以繞開權(quán)限限制)所以這些方法必須聲明為priate萧落,以至于供ObjectOutputStream來使用践美。
對希望采用自定義序列化的字段用transient修飾,然后在先調(diào)用writeObject和readObject方法中對transient修飾的字段進(jìn)行序列化铐尚,并在方法最開始調(diào)用defaultReadObject和defaultReadObject方法拨脉,對其他字段采用默認(rèn)序列化方式。這樣的好處是方便兼容宣增。
被transient修飾的成員,只是不能被默認(rèn)的序列化方法序列化(從源碼中也可以看到)矛缨,但卻可以被自定義的序列化方法序列化爹脾。

序列化的存儲規(guī)則
public class StoreRuleDemo {

    public static void main(String[] args) throws IOException{

        ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream(new File("user")));
        User user=new User();
        user.setName("taofut");
        user.setAge(27);
        user.setSex("男");
        outputStream.flush();
        outputStream.writeObject(user);
        System.out.println(new File("user").length());

        outputStream.writeObject(user);
        outputStream.flush();
        outputStream.close();
        System.out.println(new File("user").length());
    }
    //執(zhí)行結(jié)果:
    //89
    //94
}

我們發(fā)現(xiàn),同一對象兩次寫入文件箕昭,打印出寫入一次對象后的存儲大小和寫入兩次后的存儲大小灵妨,第二次寫入對象時文件只增加了5個字節(jié)。
這是因為落竹,Java 序列化機(jī)制為了節(jié)省磁盤空間泌霍,具有特定的存儲規(guī)則,當(dāng)寫入文件為同一對象時述召,并不會再將對象的內(nèi)容進(jìn)行存儲朱转,而只是再次存儲一份引用,上面增加的5個字節(jié)的存儲空間就是新增引用和一些控制信息的空間积暖。反序列化時藤为,恢復(fù)引用關(guān)系,該存儲規(guī)則極大的節(jié)省了存儲空間夺刑。

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

1》淺克隆機(jī)制:被復(fù)制對象的所有變量都含有與原來的對象相同的值缅疟,而所有的對其他對象的引用仍然指向原來的對象分别。

public class CloneDemo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Email email=new Email();
        email.setContent("今天晚上6點開會");
        Person p1=new Person("taofut");
        p1.setEmail(email);

        Person p2=p1.clone();
        p2.setName("fut");
        p2.getEmail().setContent("今天晚上6點半開會");
        System.out.println(p1.getName()+"->"+p1.getEmail().getContent());
        System.out.println(p2.getName()+"->"+p2.getEmail().getContent());
    }
    //執(zhí)行結(jié)果:
    //taofut->今天晚上6點半開會
    //fut->今天晚上6點半開會
}

以上案例很好的說明了,淺克隆不能復(fù)制新的引用存淫,Email引用還是指向的同一個對象耘斩,這就導(dǎo)致了都是”今天晚上6點半開會”的結(jié)果出現(xiàn)。

2》深克隆機(jī)制:被復(fù)制對象的所有變量都含有與原來的對象相同的值桅咆,除去那些引用其他對象的變量煌往。那些引用其他對象的變量將指向被復(fù)制過的新對象,而不再是原有的那些被引用的對象轧邪。換言之刽脖,深拷貝把要復(fù)制的對象所引用的對象都復(fù)制了一遍。

public class Email implements Serializable{
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

public class Person implements Cloneable,Serializable{
    private String name;
    private Email email;

    public String getName() {
        return name;
    }

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

    public Email getEmail() {
        return email;
    }

    public void setEmail(Email email) {
        this.email = email;
    }

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

    public Person(String name) {
        this.name = name;
    }
    //深克隆方法
    public Person deepClone() throws IOException,ClassNotFoundException{
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        ObjectOutputStream outputStream=
                new ObjectOutputStream(bos);
        outputStream.writeObject(this);

        ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream inputStream=new ObjectInputStream(bis);
        return (Person)inputStream.readObject();
    }
}

public class DeepDemo {

    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Email email=new Email();
        email.setContent("今天晚上6點開會");
        Person p1=new Person("taofut");
        p1.setEmail(email);

        Person p2=p1.deepClone();
        p2.setName("fut");
        p2.getEmail().setContent("今天晚上6點半開會");
        System.out.println(p1.getName()+"->"+p1.getEmail().getContent());
        System.out.println(p2.getName()+"->"+p2.getEmail().getContent());
    }
    //執(zhí)行結(jié)果:
    //taofut->今天晚上6點開會
    //fut->今天晚上6點半開會
}

由于Java本身提供的序列化機(jī)制存在兩個問題

  1. 序列化的數(shù)據(jù)比較大忌愚,傳輸效率低
  2. 其他語言無法識別和對接
    所以出現(xiàn)了一些序列化框架,種類包括:
    XML 序列化,JSON 序列化框架 ,Hessian 序列化框架 ,Avro 序列化 ,kyro 序列化框架 ,Protobuf 序列化框架
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曲管,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子硕糊,更是在濱河造成了極大的恐慌院水,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件简十,死亡現(xiàn)場離奇詭異檬某,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)螟蝙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門恢恼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胰默,你說我怎么就攤上這事场斑。” “怎么了牵署?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵漏隐,是天一觀的道長。 經(jīng)常有香客問我奴迅,道長青责,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任取具,我火速辦了婚禮脖隶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘者填。我一直安慰自己浩村,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布占哟。 她就那樣靜靜地躺著心墅,像睡著了一般酿矢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怎燥,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天瘫筐,我揣著相機(jī)與錄音,去河邊找鬼铐姚。 笑死策肝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隐绵。 我是一名探鬼主播之众,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼依许!你這毒婦竟也來了棺禾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤峭跳,失蹤者是張志新(化名)和其女友劉穎膘婶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛀醉,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡悬襟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拯刁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脊岳。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖筛璧,靈堂內(nèi)的尸體忽然破棺而出逸绎,到底是詐尸還是另有隱情,我是刑警寧澤夭谤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站巫糙,受9級特大地震影響朗儒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜参淹,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一醉锄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浙值,春花似錦恳不、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规求。三九已至,卻和暖如春卵惦,著一層夾襖步出監(jiān)牢的瞬間阻肿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工沮尿, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留丛塌,地道東北人。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓畜疾,卻偏偏與公主長得像赴邻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啡捶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,878評論 0 24
  • Valentine 轉(zhuǎn)載請標(biāo)明出處届慈。 序列化的意義 Java 平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java 對象徒溪,但一...
    valentine_liang閱讀 892評論 0 0
  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程金顿;反序列化de...
    步積閱讀 1,444評論 0 10
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化臊泌,必須實現(xiàn)Serializable接口。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,414評論 1 3
  • 在夜店門外揍拆,美麗姑娘 他發(fā)現(xiàn)你長得似一串最貴最甜的葡萄 或者那串最漂亮的葡萄像你寄世的胴體 他說他可以愛你 可不像...
    錢方軍閱讀 396評論 3 14