java序列化学习笔记

概念

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。

这两个过程结合起来,可以轻松地存储和传输数据。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

目的

1、以某种存储形式使自定义对象持久化,在MVC模式中很是有用;

2、将对象从一个地方传递到另一个地方;

实现方法

实现 java.io.Serializable 接口

序列化时,需要用到对象输出流ObjectOutputStream ,然后通过文件输出流构造 ObjectOutputStream 对象调用writeObject写入到文件

反之,反序列化时用到对象输入流ObjectIntputStream, 然后通过文件输出流构造 ObjectIntputStream对象调用readObject加载到内存

注意

如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,那样会报错。

实现序列化的要求

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。

实现Java对象序列化与反序列化的方法

假定一个Student类,它的对象需要序列化,可以有如下三种方法:

方法一:若Student类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化

ObjectOutputStream采用默认的序列化方式,对Student对象的非 transient的实例变量进行序列化。

ObjcetInputStream采用默认的反序列化方式,对Student对象的非transient的实例变量进行反序列化。

方法二:若Student类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。

ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。

方法三:若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。

ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。

序列化

//创建一个对象输出流,将对象输出到文件

ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));

out.writeObject("序列化日期是:"); //序列化一个字符串到文件

out.writeObject(new Date()); //序列化一个当前日期对象到文件

UserInfo user=new UserInfo("renyanwei","888888",20);

out.writeObject(user); //序列化一个会员对象

out.close();

反序列化

//创建一个对象输入流,从文件读取对象

ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));

//注意读对象时必须按照序列化对象顺序读,否则会出错

//读取字符串,对应上边第一个塞进去的东西,一个字符串,也可看做是一个对象

String today=(String)(in.readObject());

System.out.println(today);

//读取日期对象,对应上边第二个塞进去的东西,一个时间的对象

Date date=(Date)(in.readObject());

System.out.println(date.toString());

//读取UserInfo对象并调用它的toString()方法,对应上边第三个塞进去的东西,一个UserInfo的对象

UserInfo user=(UserInfo)(in.readObject());

System.out.println(user.toString());

in.close();

关于序列化,还有几个问题需要注意

1、序列化 ID 问题

比如,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。

原因就是,虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID 是否一致(就是 private static final long serialVersionUID = 1L)。即使两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。

看代码理解private static final long serialVersionUID


importjava.io.Serializable;

publicclassAimplementsSerializable {

privatestaticfinallongserialVersionUID = 1L;

privateString name;

publicString getName() {

returnname;

}

publicvoidsetName(String name) {

this.name = name;

}

}

importjava.io.Serializable;

publicclassAimplementsSerializable {

privatestaticfinallongserialVersionUID = 2L;

privateString name;

publicString getName() {

returnname;

}

publicvoidsetName(String name) {

this.name = name;

}

}

特性使用案例

想必很多人了解设计模式中的门面模式吧,也叫作Façade模式

门面模式

Façade模式是为一个复杂的模块或者子系统提供一个供外界访问的接口

相当于外界对于子系统的操作就是黑箱子操作

比如...

Client 端通过Façade Object 才可以与业务逻辑对象进行交互。而客户端的 Façade Object 不能直接由 Client 生成,而是需要Server 端生成,然后序列化后通过网络将二进制对象数据传给 Client,Client 负责反序列化得到 Façade 对象。

该模式可以使得Client 端程序的使用需要服务器端的许可,同时 Client 端和服务器端的 Façade Object类需要保持一致。当服务器端想要进行版本更新时,只要将服务器端的 Façade Object 类的序列化 ID 再次生成,当 Client端反序列化 Façade Object 就会失败,也就是强制 Client 端从服务器端获取最新程序。

Transient 关键字问题

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值还是原来的值

Externalizable

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。
JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效,此时使用该接口时,序列化的细节需要由程序员去完成。


public class Person implements Externalizable {

private String name =null;

transientprivateInteger age =null;

privateGender gender =null;

publicPerson() {

System.out.println("none-arg constructor");

}

publicPerson(String name, Integer age, Gender gender) {

System.out.println("arg constructor");

this.name = name;

this.age = age;

this.gender = gender;

}

privatevoidwriteObject(ObjectOutputStream out)throwsIOException {

out.defaultWriteObject();

out.writeInt(age);

}

privatevoidreadObject(ObjectInputStream in)throwsIOException, ClassNotFoundException {

in.defaultReadObject();

age = in.readInt();

}

@Override

publicvoidwriteExternal(ObjectOutput out)throwsIOException {

}

@Override

publicvoidreadExternal(ObjectInput in)throwsIOException, ClassNotFoundException {

}

}

如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。

另外,使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。

由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

对上述Person类进行进一步的修改,使其能够对name与age字段进行序列化,但忽略掉gender字段,如下代码所示:

    public class Person implements Externalizable {  
     
        private String name = null;  
     
        transient private Integer age = null;  
     
        private Gender gender = null;  
     
        public Person() {  
            System.out.println("none-arg constructor");  
        }  
     
        public Person(String name, Integer age, Gender gender) {  
            System.out.println("arg constructor");  
            this.name = name;  
            this.age = age;  
            this.gender = gender;  
        }  
     
        private void writeObject(ObjectOutputStream out) throws IOException {  
            out.defaultWriteObject();  
            out.writeInt(age);  
        }  
     
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
            in.defaultReadObject();  
            age = in.readInt();  
        }  
     
        @Override 
        public void writeExternal(ObjectOutput out) throws IOException {  
            out.writeObject(name);  
            out.writeInt(age);  
        }  
     
        @Override 
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
            name = (String) in.readObject();  
            age = in.readInt();  
        }  
        ...  
    } 

对敏感字段加密

问题背景
服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

解决
在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

以下是自定义在 writeObject和readObject加密解密的过程

private void writeObject(ObjectOutputStream out) {
    try {
        PutField putFields = out.putFields();
        System.out.println("原密码:" + password);
        password = "encryption";//模拟加密
        putFields.put("password", password);
        System.out.println("加密后的密码" + password);
        out.writeFields();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
 
private void readObject(ObjectInputStream in) {
    try {
        GetField readFields = in.readFields();
        Object object = readFields.get("password", "");
        System.out.println("要解密的字符串:" + object.toString());
        password = "pass";//模拟解密,需要获得本地的密钥
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
 
}

参考文献:深入理解java序列化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 一、 序列化和反序列化概念 Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化de...
    步积阅读 1,398评论 0 10
  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,741评论 0 24
  • 如果你只知道实现 Serializable 接口的对象,可以序列化为本地文件。那你最好再阅读该篇文章,文章对序列化...
    jiangmo阅读 429评论 0 2
  • 正如前文《Java序列化心得(一):序列化设计和默认序列化格式的问题》中所提到的,默认序列化方法存在各种各样的问题...
    登高且赋阅读 8,126评论 0 19
  • 10.8卡 熊志华~【日精进打卡第18天】 深圳慧友冠源科技有限公司 【知~学习】 行动+付出+方法=收获 (每天...
    华仁阅读 251评论 0 0