2019独角兽企业重金招聘Python工程师标准>>>

JDK序列化

在分布式架构中,序列化是分布式的基础构成之一,我们需要把单台设备上的数据通过序列化(编码、压缩)后通过网络传输给网络中的其它设备,从而实现信息交换。 JDK对Java中的对象序列化提供了支持,原生的Java序列化要求序列化的类必须实现java.io.Serializable接口,该接口是一个标记接口(不包含任何方法)。 下面定义一个POJO类(仅用于演示,没有任何实际意义),它将被序列化和反序列化

public class Data implements Serializable {private Integer a;private Long b;private Float c;private Double d;private Boolean e;private Character f;private Byte g;private Short h;private int a0;private long b0;private float c0;private double d0;private boolean e0;private char f0;private byte g0;private short h0;private String i;private Date j;// getter / setter ...}

使用Java序列化代码非常简单,我们需要构造一个ObjectOutputStream,该类接收一个输出流(用于输出序列化后的对象信息),这里为了方便演示,我用了ByteArrayOutputStream,将对象序列为一个字节数组

        // 执行序列化ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream output = new ObjectOutputStream(baos);output.writeObject(data);baos.close();output.close();byte[] buf = baos.toByteArray();assertEquals(947, buf.length);

代码里省略了构造测试对象的代码(属性有点多),演示了序列化的过程,除了构造输出流和关闭注流代码,实际序列化代码只有一句:output.writeObject(data);,所以Java的序列化代码实现还是比较简单的。 测试代码中包含一个关于序列化后数据大小的测试,有947个字节,后面其它的序列化会与之形成对比。 当网络一端接收到这个字节数组(数据流)后,会执行反序列化,得到序列化前的数据,下面实现反序列化

        // 执行反序列化ByteArrayInputStream bais = new ByteArrayInputStream(buf);ObjectInputStream input = new ObjectInputStream(bais);Data data2 = (Data) input.readObject();bais.close();input.close();assertFalse(data == data2);assertEquals(data.getA(), data2.getA());assertEquals(data.getI(), data2.getI());assertEquals(data.getJ(), data2.getJ());

代码里实现了将字节数组反序列化为一个Data对象,测试语句证明了反序列化对象与原对象不是一个对象(之前讲对象克隆时提到过可以使用序列化、反序列化来实现,这里证明了这一点),但其属性都是一致的,也就是说我们正确得到了序列化前的数据。

使用Serializable实现序列化时,如果某一个或某几个字段不需要序列化,可以使用transient关键字修改字段即可

private transient String password;

JDK还提供另一种序列化方式,通过Externalizable接口来实现

public class Data3 implements Externalizable {private Integer id;private String name;private Date birthday;@Overridepublic void writeExternal(ObjectOutput output) throws IOException {output.writeInt(this.id);output.writeUTF(this.name);output.writeObject(this.birthday);}@Overridepublic void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {this.id = input.readInt();this.name = input.readUTF();this.birthday = (Date) input.readObject();}// getter / setter ...}

这里不解释,其与Hadoop提供的序列化机制几乎相同,所以请参考Hadoop的序列化。

Hadoop序列化

在Hadoop中由于经常需要向DataNode复制数据,Hadoop设计了一套特殊的序列化代码(实际仍是完全由JDK实现,其实现方式与Externalizable机制基本类似)。

public class Data2 {private Integer a;private Long b;private Float c;private Double d;private Boolean e;private Character f;private Byte g;private Short h;private int a0;private long b0;private float c0;private double d0;private boolean e0;private char f0;private byte g0;private short h0;private String i;private Date j;public byte[] serialize() throws IOException {return Data2.serialize(this);}/*** 序列化当前对象** @return*/public static final byte[] serialize(Data2 data) throws IOException {assert data != null;ByteArrayOutputStream baos = new ByteArrayOutputStream();DataOutput output = new DataOutputStream(baos);// 序列化的数据参考 JdkSerializeTest 中的Data对象// 序列化、反序列化的过程都是一个字段一个字段的实现,虽然繁琐,但序列化后的大小和性能都比JDK原生序列化API强很多output.writeInt(data.getA());output.writeInt(data.getA0());output.writeLong(data.getB());output.writeLong(data.getB0());output.writeFloat(data.getC());output.writeFloat(data.getC0());output.writeDouble(data.getD());output.writeDouble(data.getD0());output.writeBoolean(data.getE());output.writeBoolean(data.isE0());output.writeChar(data.getF());output.writeChar(data.getF0());output.writeByte(data.getG());output.writeByte(data.getG0());output.writeShort(data.getH());output.writeShort(data.getH0());writeString(output, data.getI());// 序列化日期时使用时间戳表示output.writeLong(data.getJ().getTime());return baos.toByteArray();}/*** 反序列化 Data2 对象** @param buf* @return*/public static final Data2 deserialize(byte[] buf) throws IOException {// 执行反序列化,注意读取的顺序与写入的顺序要一致ByteArrayInputStream bais = new ByteArrayInputStream(buf);DataInput input = new DataInputStream(bais);Data2 data = new Data2();data.setA(input.readInt());data.setA0(input.readInt());data.setB(input.readLong());data.setB0(input.readLong());data.setC(input.readFloat());data.setC0(input.readFloat());data.setD(input.readDouble());data.setD0(input.readDouble());data.setE(input.readBoolean());data.setE0(input.readBoolean());data.setF(input.readChar());data.setF0(input.readChar());data.setG(input.readByte());data.setG0(input.readByte());data.setH(input.readShort());data.setH0(input.readShort());data.setI(readString(input));data.setJ(new Date(input.readLong()));return data;}/*** 向 DataOutput 写入字符类型稍微复杂一些** @param out* @param s* @throws IOException* @see org.apache.hadoop.io.WritableUtils#writeString(DataOutput, String)*/private static final void writeString(DataOutput out, String s) throws IOException {if (s != null) {byte[] buffer = s.getBytes("UTF-8");int len = buffer.length;// 先写入字符串长度out.writeInt(len);// 再写入字符串内容(字节数组)out.write(buffer, 0, len);} else {out.writeInt(-1);}}/*** 与 writeString(DataOutput, String) 方法相反,用于读取字符串类型数据** @param in* @return* @throws IOException* @see #writeString(DataOutput, String)*/private static final String readString(DataInput in) throws IOException {int length = in.readInt();if (length == -1) return null;byte[] buffer = new byte[length];in.readFully(buffer);      // could/should use readFully(buffer,0,length)?return new String(buffer, "UTF-8");}// getter / setter ...}

代码里实现了序列化和反序列化逻辑,Data2是一个POJO类,与上例中的Data类属性完全一样,只是多了序列化和反序列化方法(这两个方法写在POJO类中的原因是其序列化、反序列化有顺序要求,放在外面会难以控制)。 从实现代码中发现实际序列化、反序列化是由DataOutputDataInput两个接口及其实现类来实现的,这些类完全由JDK提供,并不依赖任何第三方的库,由于手动控制了序列化、反序列化,所以其性能和序列化后的大小控制都非常好

        // 序列化的数据参考 JdkSerializeTest 中的Data对象// 序列化、反序列化的过程都是一个字段一个字段的实现,虽然繁琐,但序列化后的大小和性能都比JDK原生序列化API强很多byte[] buf = data.serialize();// 测试序列化大小:JDK序列化后是947,这里只有204assertEquals(204, buf.length);// 执行反序列化,注意读取的顺序与写入的顺序要一致Data2 data2 = Data2.deserialize(buf);assertFalse(data == data2);assertEquals(data.getA(), data2.getA());assertEquals(data.getA0(), data2.getA0());// 由于浮点数在计算时会有误差,这里第三个参数用于控制误差assertEquals(data.getC(), data2.getC(), 0.0);assertEquals(data.getC0(), data2.getC0(), 0.0);assertEquals(data.getE(), data2.getE());assertEquals(data.isE0(), data2.isE0());assertEquals(data.getF(), data2.getF());assertEquals(data.getF0(), data2.getF0());assertEquals(data.getG(), data2.getG());assertEquals(data.getG0(), data2.getG0());assertEquals(data.getH(), data2.getH());assertEquals(data.getH0(), data2.getH0());assertEquals(data.getI(), data2.getI());assertEquals(data.getJ(), data2.getJ());

可以看出同样对象序列化后只有204个字节,约为之前的1/4,而且序列化的性能也调出很多,后面会给出简单对比。

Hessian序列化

在一些开源框架中(如:Dubbo),也使用Hessian库(这里指的是Hessian2)来实现序列化。

        // 执行序列化ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output hessian2Output = new Hessian2Output(baos);hessian2Output.writeObject(data);hessian2Output.close();// 获取字节数组前,必须先关闭Hessian2Output,否则取得字节数组长度为0(原因暂不清楚)byte[] buf = baos.toByteArray();baos.close();// 测试断言Assert.assertNotNull(buf);Assert.assertEquals(373, buf.length);System.out.println(new String(buf));// 执行反序列化Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(buf));Data data2 = (Data) hessian2Input.readObject();hessian2Input.close();// 测试断言assertFalse(data == data2);assertEquals(data.getA(), data2.getA());assertEquals(data.getI(), data2.getI());assertEquals(data.getJ(), data2.getJ());

相对JDK序列化和Hadoop序列化,其序列化后的数据大小居中,实际上性能也是居中的。但该库的优势在于,其跨语言的特性,也就是说可以向非Java语言的程序发送序列化数据,并能由对应语言的Hessian库实现反序列化。

性能比较

下面使用10,000次循环序列化、反序列化(单线程)来测试三种序列化方式的耗时(该测试仅供参考,场景有限,并不能真的说明三种方式优劣程度)。

  • jdk
    @Testpublic void performance() throws IOException, ClassNotFoundException {final int loop = 10_000;long time = System.currentTimeMillis();for (int i = 0; i < loop; i++) {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream output = new ObjectOutputStream(baos);output.writeObject(data);baos.close();output.close();byte[] buf = baos.toByteArray();// 执行反序列化ByteArrayInputStream bais = new ByteArrayInputStream(buf);ObjectInputStream input = new ObjectInputStream(bais);input.readObject();bais.close();input.close();}// loop = 10,000 -> 程序执行耗时:1037 毫秒!System.out.println(String.format("程序执行耗时:%d 毫秒!", System.currentTimeMillis() - time));}
  • hadoop
    @Testpublic void performance() throws IOException {final int loop = 10_000;long time = System.currentTimeMillis();for (int i = 0; i < loop; i++) {// 执行序列化byte[] buf = data.serialize();// 执行反序列化Data2.deserialize(buf);}// loop = 10,000 -> 程序执行耗时:75 毫秒!System.out.println(String.format("程序执行耗时:%d 毫秒!", System.currentTimeMillis() - time));}
  • hessian
    public void performance() throws IOException {final int loop = 10_000;long time = System.currentTimeMillis();for (int i = 0; i < loop; i++) {// 执行序列化ByteArrayOutputStream baos = new ByteArrayOutputStream();Hessian2Output hessian2Output = new Hessian2Output(baos);hessian2Output.writeObject(data);hessian2Output.close();byte[] buf = baos.toByteArray();// 执行反序列化Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream(buf));hessian2Input.readObject();hessian2Input.close();}// loop = 10,000 -> 程序执行耗时:300 毫秒!System.out.println(String.format("程序执行耗时:%d 毫秒!", System.currentTimeMillis() - time));}

结论(非权威,有兴趣的自行研究吧) | 循环次数 | jdk (947bytes) | hadoop (204bytes) | hessian (373bytes) | | - | - | - | - | | 10,000 | 1,037ms | 75ms | 300ms |

其它序列化

实际应用中,序列化可选方案很多,像Hadoop还可以用Avro、Protobuf来进行序列化,下面列出一些常用的序列化库:

  • JSON,是一种规范,对应的库非常多,比如:Jackson、Fastjson等
  • Avro,Hadoop提供的一套跨平台序列化方案
  • Protobuf,Google提供的一套跨平台序列化方案
  • Thrift,Apache提供的一套跨平台序列化方案
  • Kryo
  • FST
  • Dubbo 后面三个都只能用于Java,其中Dubbo是Dubbo框架提供的序列化方案(经查阅源码,2.6.x及以后的版本中不再提供)

结语

序列化在分布式架构中(比较偏底层)是很重要的一环,好的序列化方案可以节省大量的带宽,并且提升程序处理速度。

后面列出的一些序列化方案本文未详细解释,这里先留个坑,后面将专门撰文来讲解。

源码仓库:

  • https://github.com/zlikun-jee/effective-java
  • https://gitee.com/zlikun/effective-java

转载于:https://my.oschina.net/zhanglikun/blog/1922868

Java拾遗:004 - JDK、Hadoop、Hessian序列化相关推荐

  1. java kiwi_【Java拾遗】不可不知的 Java 序列化

    [Java拾遗]不可不知的 Java 序列化 前言 在程序运行的生命周期中,序列化与反序列化的操作,几乎无时无刻不在发生着.对于任何一门语言来说,不管它是编译型还是解释型,只要它需要通讯或者持久化时, ...

  2. hadoop的序列化与java的序列化区别

    java的序列化机制 java序列化时会把具体类的数据和类的继承结构信息都序列化传递. 如下图 hadoop的序列化机制 序列化类的数据,但是不序列化类的继承结构信息. 网络传递的时候就少了很多流量, ...

  3. Java编程思想学习笔记4 - 序列化技术

    今天来学习下Java序列化和反序列化技术,笔者对<Java编程思想>中的内容,结合网上各位前辈的帖子进行了整理和补充,包括: 序列化概述 Java原生序列化技术 Hessian序列化技术 ...

  4. Hessian序列化实例

    Hessian也是一种可以做序列化的工具,他也支持多语言,这里介绍java中做序列化的示例.Hessian序列化和jdk自带的序列化,思路基本一样,只不过Hessian序列化之后,字节数更小,性能更优 ...

  5. Hessian RPC示例和基于Http请求的Hessian序列化对象传输

    本文主要介绍两个案例,第一个是使用Hessian来实现远程过程调用,第二个是通过Hessian提供的二进制RPC协议进行和Servlet进行数据交互,Hessian本身即是基于Http的RPC实现. ...

  6. java.library.path hadoop_java - Hadoop“无法为您的平台加载native-hadoop库”警告

    java - Hadoop"无法为您的平台加载native-hadoop库"警告 我目前正在运行CentOs的服务器上配置hadoop. 当我运行hadoop-env.sh或sto ...

  7. 简单了解各种序列化技术-Hessian序列化框架

    Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来说,Hessian具有更好的性能和易用性,而且支持多种不同的语言 实际上Dubbo采用的就是Hessian序列化来 ...

  8. Hadoop--Linux环境下JDK/Hadoop的安装与配置

    Linux环境下安装Hadoop / jdk: 1.进入Xshell–>打开Hadoop102终端–>cd software–>点击Xftp–>将压缩包拖入Hadoop102/ ...

  9. Java 14:JDK 14进入GA时的所有新功能

    是的,六个月过去了这么快,现在到了,Java 14的发布即将到来. 我们一直在跟踪新JDK在过去半年中的进展,您可以在此处找到摘要的所有功能. 但是,如果您想直接进入,可以在此处找到JDK 14二进制 ...

最新文章

  1. 【新拟态】左上角标签样式、ICON图标样式、模仿AppStore的应用图标
  2. Qt 不再使用 LGPLv2.1 授权
  3. 理解并解决IE的内存泄漏方式[翻译2]
  4. c语言错误 xef代表什么,单片机C语言代码手册 含100多个经典C程序
  5. git 基本命令记录
  6. JQuery使用笔记
  7. python学生管理系统(函数方法)_(python函数)学生管理系统
  8. R语言--字符串操作
  9. 【疾病分类】基于matlab GUI模糊逻辑分类叶病严重程度分级系统【含Matlab源码 194期】
  10. unity图片模糊处理
  11. Delphi第三方控件大测评
  12. cocos2d AABB碰撞检测
  13. Python网络编程自动化(HCIA)
  14. 日记侠:你对微信关键词是如何理解的?
  15. 蟠桃c语言,【蟠桃记】 (C语言代码)递归法和归纳法
  16. 解决非系统盘出现Program Files文件夹以及Program Files下的ModifiableWindowsApps文件夹无法删除的问题。
  17. 课堂问题:一个凸函数的性质
  18. macOS Catalina 10.15.6(19G2021)原版镜像CDR下载
  19. 成都市计算机大学排名,成都东软学院排名2021 四川排名第34全国排名第781
  20. EEG-fMRI 融合相关软件推荐

热门文章

  1. 2017.12.29会议记录
  2. SQL SERVER存储过程,删除字段的同时删除其约束和外键
  3. poj 1837(blance)
  4. Word DocX 模板数据填充 .NET Word 报表
  5. centos7 防火墙(关闭、开启、开机关闭、开机自启等)
  6. [Vue warn]: Error in v-on handler: “ReferenceError: addForm is not defined“
  7. 高并发和大数据下的高级算法与数据结构:如何快速获取给定年龄区间的微信用户数量或快速获取美团中购买量前k的品类
  8. 安卓系统android使用C# .NET Xamarin框架调用相机拍照
  9. 第II章、如何建立站点?
  10. IFNULL和NULLIF