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

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

序列化和反序列化是将对象转化成字节数组以方便保存或者用于网络传输,这个对象可以是一个图片,一个字符串,一个class等等,常见的序列化格式有字节数组,json格式,xml格式,更加高效的有google开源的Protocol Buffers,以及Apache Avro。

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

(1)实现数据持久化,一般jvm的里面数据,在java程序退出时,所有的状态都不会保留,通过序列化可以将需要的数据给持久化到磁盘文件或者数据库,这样就可以在下次jvm启动的时候再把数据重新还原出来。

(2)利用序列化实现远程通信,即在网络上传送对象的字节序列,这种场景一般在socket或者rpc的服务中比较常见。

(三)Java里面如何实现序列化和反序列化

在java里面有两种方式可以实现对象的序列化:

(1)实现Serializable接口的类,jdk会自动帮我们序列化该类所有的信息, 但如果用户定义了writeObject和readObject方法,那么在序列化和反序列化的时候会通过反射优先调用自定义的方法

(2)实现Externalizable接口的类,需要用户自定义序列化和反序列化的逻辑,分别重写writeExternal和readExternal方法。

下面看一个例子,首先我们定义一个Person类并实现了序列化接口:

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();
}

}

然后我们定义了帮助实现序列化和反序列化的工具类:

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 {

/**
 * 将任何实现了序列化接口的对象转成字节数组
 * @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();
    
}

/**
 * 将任何序列化的字节数组给还原成对象
 * @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;
}

/**
 * 将一个实现了序列化的对象给序列化成文件
 * @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);
    
}

/**
 * 将序列化对象还原成文件
 * @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;
    
    
}

}

然后看下我们的测试类,分别测试文件的序列化和字节的序列化

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);//文件测试序列化和反序列化
    System.out.println("======================================");
    byteTest(person);//字节测试序列化和反序列化

}


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("基于字节序列化前:" + p1);
    byte[] bytes = SerializeTools.toBytes(p1);//序列化成字节数组    
    Person p2 = (Person)SerializeTools.toObj(bytes);//反序列化成对象
    System.out.println("基于字节反序列化后:" + p2);

}

}

运行后输出如下:

基于文件序列化前:Person [address=北京海淀, name=张三, age=25]
基于文件序列化后:Person [address=null, name=张三, age=25]
======================================
基于字节序列化前:Person [address=北京海淀, name=张三, age=25]
基于字节反序列化后:Person [address=null, name=张三, age=25]

细心的同学可能已经发现地址这个字段,在反序列化后字段值丢失了,这里说明下:

(1)在java里面transient关键词修饰的成员变量是不会被序列化的,这一点在上面的输出中已经得到验证了,注意transient关键词只能修饰成员变量,不能修饰类和方法

(2)在java里面static关键词修饰的字段也是不会被序列化的,因为这个是类的字段,而序列化是针对对象的。

引申一下:java的内存分配有栈和堆以及永久代,栈中存放基本变量,数组和对象引用,堆中存放对象,当有static修饰的变量或方法会被放到永久代里面。它先于对象而存在,不依赖实例,无论是变量,方法,还是代码块,只要用static修饰,就是在类被加载时就已经准备好了,也就是可以被使用或者已经被执行,都可以脱离对象而执行,所以在类加载时静态变量的值其实已经还原出来了之后才是反序列化出来成员变量的值。

(3)在上面的Person类里面,相信大家还看到了一个用static final long修饰的 serialVersionUID字段,这个字段的功能是用来标识类版本的兼容性:

举个例子,假如现在没有定义serialVersionUID这个字段,jdk默认是根据类信息计算一个版本值,在类已经被序列化成文件后,我们又修改了类结构,比如新增了几个字段,这个时候拿着新版本的类去反序列化旧版本的类,就会抛出下面的异常:

意思就是版本不一致,导致失败,如果我们定义这个值,并且新旧版本的值一样,不管新增没新增字段,都可以反序列化成功,默认新增字段的值是jdk给成员变量初始化的值,比如字符串就是null。

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

上面提到过实现了Serializable接口的类,我们可以重写下面的方法来自定义序列化逻辑:

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();
}

再次执行测试方法,输出结果如下:

基于文件序列化前:Person [address=北京海淀, name=张三, age=25]
call write
call read
基于文件序列化后:Person [address=北京海淀, name=张三, age=25]
======================================
基于字节序列化前:Person [address=北京海淀, name=张三, age=25]
call write
call read
基于字节反序列化后:Person [address=北京海淀, name=张三, age=25]

这次我们发现了被transient修饰的address字段竟然也有值了,为什么?因为我们自定义序列化的时候把地址也给序列化了,所以这个时候无论你用不用transient关键词都无关紧要了。

注意如果实现了上面的方法其实和使用Externalizable就相差无几了,所以在这里不再给出Externalizable的例子

(五)什么时候应该readObject和writeObject

在effective java里面提到过:

当一个对象的物理表示方法与它的逻辑数据内容有实质性差别时,使用默认序列化形式有N种缺陷。

其实是建议我们重写的,这样可以更好的控制序列化的过程,如果能减少一些不必要的序列化的字段,其实对我们的程序性能也是一种提升。

总结:

本文介绍了Java里面序列化和反序列化功能和使用以及一些注意事项,序列化其实还提供了深度克隆的功能,尤其是当类里面的引用层次比较多及引用特别复杂的时候,通过序列化来深度拷贝对象也是一种比较便利的方法,除此之外,我们还应该知道序列化和反序列化和反射一样,弱化了java安全权限修饰符的作用,无论你privte还是protected修饰的字段,在序列化和反射面前都是无作用的,所以一些敏感信息的序列化尤其是在网络上传输的如密码,金钱什么的,都应该考虑加密或者其他安全措施。

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

推荐阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 10,842评论 0 24
  • 官方文档理解 要使类的成员变量可以序列化和反序列化,必须实现Serializable接口。任何可序列化类的子类都是...
    狮_子歌歌阅读 2,396评论 1 3
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,160评论 1 44
  • 我最近的‘写作‘’表现真的无法堪称写作,无 论在选题上,标题上,架构上,排版上都 无突破或者毫无用心之处,那...
    王海岭阅读 184评论 0 0
  • “刚刚把孩子揍了一顿!明明可以考100分,却都做错了!” “看到孩子拿回来的数学试卷,顿时火冒三丈!现在血压一定贼...
    嘤说亲子阅读 1,030评论 0 0