当前位置: 首页 > 科技观察

数据方面,序列化框架评测报告

时间:2023-03-17 00:36:38 科技观察

序列化是我们日常开发中经常用到的技术,比如需要在内存中持久化存储对象,通过网络将对象传输到远端。目前市面上序列化框架众多,开发团队在进行技术选型时往往难以取舍,甚至可能踩坑。今天选取市面上常用的几款序列化框架进行测试对比,帮助开发团队了解在不同场景下使用哪种序列化框架。测试对比有四种框架:JDKnative、fastjson、Kryo、Protobuf。下面将从以下四个方面给出详细的测试对比结果:(1)是否通用:是否支持跨语言、跨平台;(2)是否好用:是否编译、使用、调试;(3)性能是否好:序列化性能主要包括时间开销和空间开销,时间开销是指序列化和反序列化对象所花费的时间,空间开销是指序列化生成的数据大小;(4)可扩展性是否强:随着业务的发展,传输的业务对象可能会发生变化,比如增加新的字段。这个时候就要看选择的序列化框架是否有很好的扩展性;框架一:JDK原生通用吗?JDK原生是Java自带的序列化框架,和Java语言是强绑定的。通过JDK序列化的对象不能返回给其他语言序列化,所以它的通用性比较差。这个容易用吗?实现了java.io.Serializable序列化接口的类,意味着该类的对象可以被序列化,否则会报错。简要看一下Serializable类。通过查看源码我们知道Serializable只是一个空接口,并没有定义任何方法。publicinterfaceSerializable{}这说明Serializable只是一个标识函数,用来告诉JVM这个对象可以被序列化。如果要真正完成对象的序列化和反序列化,就得用到IO核心操作类:ObjectOutputStream和ObjectInputStream。/***序列化**@paramobj待序列化的对象*@return二进制字节数组*@throwsIOException*/publicstaticbyte[]serialize(Objectobj)throwsIOException{//字节输出流ByteArrayOutputStreambyteArrayOutputStream=newByteArrayOutputStream();//将对象序列化为二进制字节流ObjectOutputStreamobjectOutputStream=newObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(obj);//获取二进制字节数组byte[]bytes=byteArrayOutputStream.toByteArray();//关闭流objectOutputStream.close();byteArrayOutputStream.close();返回字节;}ObjectInputStream类的readObject()方法用于从IO流中读取对象,完成对象反序列化:/***反序列化**@parambytes待反序列化的二进制字节数组*@param反序列化后的对象类型*@return反序列化对象*@throwsIOException*@throwsClassNotFoundException*/publicstaticTdeSerialize(byte[]bytes)throwsIOException,ClassNotFoundException{//字节输入流finalByteArrayInputStreambyteArrayInputStream=newByteArrayInputStream(bytes);//将二进制字节流反序列化为对象finalObjectInputStreamobjectInputStream=newObjectInputStream(byteArrayInputStream);finalTobject=(T)objectInputObject(Stream.read)//关闭流objectInputStream.close();byteArrayInputStream.close();返回对象;}从上面的代码可以看出,JDK原生框架使用起来还是有点繁琐。首先,对象必须实现java.io.Serializable接口。其次,需要IO流操作来完成序列化和反序列化与市面上其他开源框架相比,上面的代码非常生硬。一句话总结:JDK原生框架的易用性稍差。性能好吗?(1)序列化卷测试为了方便测试对比,我定义了一个普通的java类,后面其他框架的测试基本都会用到:publicclassUserDTOimplementsSerializable{privateStringname;私有字符串wechatPub;私有字符串作业;...}实例化UserDTO类:UserDTOuserDTO=newUserDTO();userDTO.setName("雷小帅");userDTO.setWechatPub("微信公众号:爱笑的建筑师");userDTO.setJob("优秀码农");序列化和反序列化测试:System.out.println("---1.jdknativetest---");byte[]bytes=JDKSerializationUtil.serialize(userDTO);System.out.println("序列化成功:"+Arrays.toString(bytes));System.out.println("bytesize="+bytes.length);UserDTOuserDTO1=JDKSerializationUtil.deSerialize(bytes);System.out.println("反序列化成功:"+userDTO1);打印结果:---1.jdknativetest---序列化成功:[-84,-19,0,5,115,114,0,39,...bytesize=182反序列化成功:UserDTO[name='雷小帅',wechatPub='微信公众号:爱笑的架构师',job='优秀码农']一个UserDTO序列,转换后为182字节。和其他框架对比之后就知道这个水平太差了。java原生自带序列化工具,儿子不擅长。(2)序列化速度测试接下来我们测试一下序列化和反序列化的速度,共100万次循环:JDK序列化耗时:2314毫秒JDK反序列化耗时:4170毫秒这个结果如何?稍后揭晓。它是可扩展的吗?JDK原生序列化工具通过在类中定义serialVersionUID常量来控制版本:privatestaticfinallongserialVersionUID=7982581299541067770L;上面的serialVersionUID是IDEA工具自动生成的一个长整数。其实这个值不需要声明,JDK会根据hash算法自动生成一个。如果版本号是序列化时的当前值,反序列化前改了这个值,反序列化时会报错,提示ID不一致。如果需要在UserDTO类中再增加一个字段,如何支持扩展?您可以更改serialVersionUID的值。框架二:fastjson是通用的吗?fastjson是阿里巴巴出品的序列化框架,可以将对象序列化为JSON字符串。类似的框架还有jackson、gson等。由于JSON是语言和平台无关的,通用性好。这个容易用吗?UserDTO类不需要实现Serializable接口,也不需要添加serialVersionUID版本号,使用起来非常简单。将对象序列化为json字符串:com.alibaba.fastjson.JSON.toJSONString(obj);将一个json字符串反序列化为指定类型:com.alibaba.fastjson.JSON.parseObject(jsonString,clazz);另外fastjson框架还提供了很多注解,可以在UserDTO类中进行配置,实现一些自定义的功能需求。性能好吗?(1)序列化卷测试和JDK原生框架一样,假设我们实例化了一个UserDTO对象,分别进行序列化和反序列化测试:System.out.println("---2.fastjsontest---");StringjsonString=FastjsonSerializationUtil.serialize(userDTO);System.out.println("序列化成功:"+jsonString);System.out.println("bytesize="+jsonString.length());UserDTOuserDTO2=FastjsonSerializationUtil.deSerialize(jsonString,UserDTO.class);System.out.println("反序列化成功:"+userDTO2);上面的代码将序列化和反序列化的代码封装到了一个工具类中。运行输出结果:---2.fastjson测试---序列化成功:{"job":"ExcellentCoder","name":"雷小帅","wechatPub":"微信公众号:爱笑建筑师"}bytesize=54反序列化成功:UserDTO[name='雷小帅',wechatPub='WeChat公众号:LaughingArchitect',job='优秀码农']可以看到序列化后有54字节,而上面的JDK原生框架是182字节。经过对比,发现fastjson确实比JDK原生框架强很多。我儿子真的不好。(2)序列化速度测试序列化卷测试完成后,我们再测试序列化和反序列化速度。漫长的等待,跑完循环100万次后的实测结果如下:fastjson序列化时间:287毫秒fastjson反序列化耗时:365毫秒,结果简直,顾名思义,sofast~看看隔壁JDK原生框架的速度,惨不忍睹,哎。。。可扩展吗?fastjson没有版本控制机制。如果修改了类,比如增加了熟悉的字段,反序列化的时候配置一下,忽略不熟悉的熟悉的字段就可以正常反序列化了。所以fastjson的扩展性还是很灵活的。框架3:Kryo是通用的吗?Kryo是一个快速高效的二进制序列化框架,号称是Java领域最快的。其特点是序列化速度快、体积小、界面简单易用。Kryo支持自动深/浅拷贝,即直接通过object->object进行深拷贝,而不是object->byte->object的过程。关于Kryo的更多信息,可以去Github查看:https://img.ydisp.cn/news/20220902/3w1do4z5vrz关于通用性,Kryo是为Java语言开发的框架,跨语言使用基本困难,所以它的普遍性比较差。这个容易用吗?首先介绍一下Kryo依赖:com.esotericsoftwarekryo5.3.0Kryo提供的API非常简洁,而Output类封装了为了操作输出流,使用writeObject方法将对象写入output输出过程,完成二进制序列化过程。下面代码封装了一个简单的工具方法:/***serialization**@paramobj待序列化对象*@paramkryokryoobject*@returnbytearray*/publicstaticbyte[]serialize(Objectobj,Kryokryo){输出output=newOutput(1024);kryo.writeObject(输出,对象);output.flush();returnoutput.toBytes();}Kryo反序列化也很简单,Input封装了输入流操作,通过readObject方法从输入流中读取反序列化后的二进制到对象中。/***反序列化**@parambytes要反序列化的二进制字节数组*@param反序列化对象类型*@return反序列化对象*/publicstaticTdeSerialize(byte[]bytes,Classclazz,Kryokryo){输入input=newInput(bytes);returnkryo.readObject(input,clazz);}另外,Kryo提供了丰富的配置项,可以在创建Kryo对象时进行配置。总的来说,Kryo的使用非常简单,界面也非常好用。性能好吗?(1)连载量测试Kryo框架不同于其他框架。实例化的时候可以选择提前注册类,这样序列化和反序列化会更快。当然,您也可以选择不注册。System.out.println("---3.kryotest---");Kryokryo=newKryo();kryo.setRegistrationRequired(false);//kryo.register(UserDTO.class);byte[]kryoBytes=KryoSerializationUtil.serialize(userDTO,kryo);System.out.println("序列化成功:"+Arrays.toString(kryoBytes));System.out.println("bytesize="+kryoBytes.length);UserDTOuserDTO3=KryoSerializationUtil.deSerialize(kryoBytes,UserDTO.class,kryo);System.out.println("反序列化成功:"+userDTO3);运行结果:序列化成功:[-123,-28,-68,-104,-25,…]bytesize=60反序列化成功:UserDTO[name='雷小帅',wechatPub='微信公众号:Architectwholovestosmile',job='优秀码农']from结果序列化后一共60字节。(2)序列化速度测试序列化卷测试完成后,我们再测试序列化和反序列化速度。经过漫长的等待,循环运行100万次后的实测结果如下:kryo序列化耗时:295毫秒kryo反序列化耗时:211毫秒,还不错。它是可扩展的吗?Kryo的默认序列化器FiledSerializer不支持字段扩展。如果要使用扩展序列化器,则需要配置其他默认序列化器。框架四:Protobuf是通用的吗?Protobuf是一个由谷歌开源的二进制序列化框架。Protobuf需要先编写一个schema描述文件,然后通过编译器将其编译成特定的编程语言(Java、C++、Go等),因此它是一个语言中立、跨平台的框架,具有很好的通用性。这个容易用吗?先编写schema文件,定义一个User类,有三个属性字段:syntax="proto3";optionjava_package="com.example.demo2.serialization.protobuf";messageUser{stringname=1;字符串wechatPub=2;stringjob=3;}然后在电脑上安装Protobuf编译工具,执行编译命令:protoc--java_out=./user-message.proto编译成功后会生成一个UserMessage类。UserMessage类包含了很多内容:首先,有一个Builder内部类,可以用来实例化对象;另外提供了toByteArray(),可以方便的将对象序列化为二进制字节数组;提供了parseFrom()方法将对象反序列化转换为对象。使用界面非常简单,开箱即用。性能好吗?(1)序列化量测试使用上面生成的UserMessage类创建一个对象,然后进行序列化和反序列化测试:System.out.println("---4.protobuftest---");用户消息。Useruser=UserMessage.User.newBuilder().setName("雷小帅").setWechatPub("微信公众号:爱笑的架构师").setJob("优秀码农").build();finalbyte[]protoBufBytes=user.toByteArray();System.out.println("序列化成功:"+Arrays.toString(protoBufBytes));System.out.println("bytesize="+protoBufBytes.length);finalUserMessage。用户user1=UserMessage.User.parseFrom(protoBufBytes);System.out.println("反序列化成功:"+user1);运行结果:序列化成功:[-123,-28,-68,-104,-25,...]bytesize=63反序列化成功:UserDTO[name='雷小帅',wechatPub='微信公众号:爱笑的架构师',job='ExcellentCoder']sequence转换后是63字节,比Kryo略多,有点意外。(2)序列化速度测试序列化卷测试完成后,我们再测试序列化和反序列化速度。经过漫长的等待,循环运行100万次后实测结果如下:Protobuf序列化耗时:93毫秒Protobuf反序列化耗时:341毫秒序列化速度很强,为什么反序列化这么慢?它是可扩展的吗?可扩展性是Protobuf的设计目标之一。我们可以很方便的增删字段,新旧协议都可以解析。总结:本文对常用的框架进行了测试和比较。通过观察它们是否通用、易用、性能好、扩展性强这四个维度,我们发现它们各有优缺点。当心。最后针对性能测试,对各个框架进行简单的总结和排名。(1)序列化体积fastjson54字节