xml解析、Gson、protobuf、flatbuffers具体实践对比
情境
Android大文件反序列化
性能简单对比
解析类型 | 文件大小(样本数据量一致) | 反序列化时间具体 | 解析类型 | 传送门 |
---|---|---|---|---|
xml解析 | 1.8M | 159ms | sax/pull解析 | https://developer.android.com/reference/javax/xml/parsers/SAXParserFactory 、https://developer.android.com/reference/org/xmlpull/v1/XmlPullParser |
Json反序列化 | 1.8M | 79ms | Gson | https://github.com/google/gson |
ProtoBuffer反序列化 | 1.5M(转成二进制文件会变小) | 142ms | ProtoBuffer | https://developers.google.com/protocol-buffers |
wire | 1.5M | 140ms | Dinosaur.ADAPTER.decode | https://square.github.io/wire/ |
flatbuffers 反序列化 | 1.6M(比ProtoBuffer二进制文件稍大一点) | 2ms | flatbuffers | https://github.com/google/flatbuffers |
解析类型 | 支持文件类型 | 传送门 | |
---|---|---|---|
FastJson2 | JSON、JSONB | https://github.com/alibaba/fastjson2 | |
Gson (2.9.0) | JSON、Proto | https://github.com/google/gson | |
文件类型 | 链接 | 性能数据 | |
---|---|---|---|
JSONB | https://alibaba.github.io/fastjson2/jsonb_format_cn | https://alibaba.github.io/fastjson2/benchmark_cn | |
Proto | https://developers.google.com/protocol-buffers/docs/tutorials | https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html | |
以下数据来自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
解析性能
序列化之空间开销:
解析类型 | 支持文件类型 | compiler | 生成命令 | 依赖 |
---|---|---|---|---|
ProtoBuffer | proto | https://github.com/protocolbuffers/protobuf#protocol-compiler-installation | protoc -I=DST_DIR $SRC_DIR/addressbook.proto | implementation 'com.google.protobuf:protobuf-java:3.21.2' |
Wire | proto | https://square.github.io/wire/ | https://github.com/square/wire/blob/master/wire-library/docs/wire_compiler.md | api "com.squareup.wire:wire-runtime:4.4.0" |
flatbuffers | fbs | https://github.com/google/flatbuffers/releases | flatc -o ./ --java animal.fbs | implementation 'com.google.flatbuffers:flatbuffers-java:2.0.0' |
小结
protobuf Java版反序列化比xml解析稍好一些,比Gson反序列化耗时要长
wire
提供了Gradle插件,能根据proto schema文件直接生成Java文件。
square公司提供的api很符合开发者思维,使用起来比protoBuffer顺手多了,很愉快的就完成转换
根据相关文件、序列化、反序列化操作,其性能表现也与等量xml解析不相伯仲,目测只是使用方式变了,具体实现方式还是protobuf那一套。flatbuffers【Java版】 2ms
仅仅为2ms的解析时间,后续看了它实现的原理,就不能称为解析时间了,因为它不需要解析。
后续我又将文件大小扩容到92M,读取速度仅为47ms,这样还比1.8M的Gson解析快一倍
Flatbuffers
- https://google.github.io/flatbuffers/
- https://github.com/google/flatbuffers
这个库让人头疼的地方:
1、就是文档很不完善,操作细节全靠摸索
2、文件scheme和proto文件完全不一样
3、数据流使用的是bytebuffer (转字节数组有点坑)
4、文件使用 .fbs ,支持类型:https://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html
Built-in scalar types are
8 bit: byte (int8), ubyte (uint8), bool
16 bit: short (int16), ushort (uint16)
32 bit: int (int32), uint (uint32), float (float32)
64 bit: long (int64), ulong (uint64), double (float64)
Vector of any other type (denoted with [type]). Nesting vectors is not supported, instead you can wrap the inner vector in a table.
string, which may only hold UTF-8 or 7-bit ASCII. For other text encodings or general binary data use vectors ([byte] or [ubyte]) instead.
References to other tables or structs, enums or unions
5、fbs文件不同语言编译器:虽然文档没说,但是在release页面找到了
https://github.com/google/flatbuffers/releases 我分别下了Mac、Windows的都试过了,没啥问题
6、序列化、持久化
Flatbuffers 具体实现
1、xml.fbs文件,android studio有fbs高亮插件
namespace com.fbs.app.generated;
table Menu {
type:string;
mainmenu:MainmenuDTO;
}
table MainmenuDTO{
item:[ItemDTO];//数组
}
table ItemDTO{
code:string;
activity:string;
iconum:string;
icon:string;
type:string;
extended:string;
parentid:string;
localUrl:string;
authorityHide:string;
moreentry:string;
id:string;
iconUrl:string;
homeAuth:string;
classX:string;
msgtype:string;
order:string;
appnum:string;
packageX:string;
textSize:string;
business:string;
parentflag:string;
textPostion:string;
navigateNote:string;
constructor:string;
relationId:string;
textColor:string;
url:string;
authority:string;
name:string;
textBackground:string;
hascontent:string;
DefaultLoad:string;
menuAuthority:string;
ShowTitleLine:string;
turnViewType:string;
}
root_type Menu;
2、根据fbs文件生成java类
下载相关系统平台的编译器,执行
$ flatc -o ./ --java animal.fbs
-o 我指定了当前目录生成,我将flatc配置成了全局变量,可以指定其他目录
--java 生成java文件,也可以指定为kotlin
生成了三个文件:Menu.java MainmenuDTO.java ItemDTO.java
3、根据json Bean生成二进制文件
private void createFlatBuffersFile(Bean bean) {
FlatBufferBuilder fb = new FlatBufferBuilder(100);
int[] dataArray = new int[bean.menu.mainmenu.item.size()];
for (com.example.locationapplication.Bean.MenuDTO.MainmenuDTO.ItemDTO itemDTO : bean.menu.mainmenu.item) {
int itemOffset = ItemDTO.createItemDTO(fb,
fb.createString(itemDTO.code),
fb.createString(itemDTO.activity),
fb.createString(itemDTO.iconum),
fb.createString(itemDTO.icon),
fb.createString(itemDTO.type),
fb.createString(itemDTO.extended),
fb.createString(itemDTO.parentid),
fb.createString(itemDTO.localUrl),
fb.createString(String.valueOf(itemDTO.authorityHide)),
fb.createString("" + itemDTO.moreentry),
fb.createString(itemDTO.id),
fb.createString(itemDTO.iconUrl),
fb.createString(itemDTO.homeAuth),
fb.createString(itemDTO.classX),
fb.createString(itemDTO.msgtype),
fb.createString(itemDTO.order + ""),
fb.createString(itemDTO.appnum + ""),
fb.createString(itemDTO.packageX),
fb.createString(itemDTO.textSize + ""),
fb.createString(itemDTO.business),
fb.createString(itemDTO.parentflag + ""),
fb.createString(itemDTO.textPostion),
fb.createString(itemDTO.navigateNote),
fb.createString(itemDTO.constructor),
fb.createString(itemDTO.relationId),
fb.createString(itemDTO.textColor),
fb.createString(itemDTO.url),
fb.createString(itemDTO.authority),
fb.createString(itemDTO.name),
fb.createString(itemDTO.textBackground),
fb.createString(itemDTO.hascontent + ""),
fb.createString(itemDTO.defaultLoad + ""),
fb.createString(itemDTO.menuAuthority),
fb.createString("ShowTitleLine"),
fb.createString("turnViewType")
);
dataArray[index] = itemOffset;
index++;
}
int itemVector = MainmenuDTO.createItemVector(fb, dataArray); //这个文档没有说明,多试了几个create开头的方法,使用vector可行
int mainmenuDTO = MainmenuDTO.createMainmenuDTO(fb, itemVector);
int menuOffset = Menu.createMenu(fb,
fb.createString(bean.menu.type),
mainmenuDTO
);
fb.finish(menuOffset);//根据文档来的
ByteBuffer byteBuffer = fb.dataBuffer(); //到此处已经映射完成
outPutTofile(byteBuffer, "/xml_flatbuffer");//打算将文件写入到包名下的files目录下,文件名为xml_flatbuffer
}
/**
* 将解析数据持久化到文本
* @param byteBuffer
* @param filename
*/
private void outPutTofile(ByteBuffer byteBuffer, String filename) {
// byte[] array = byteBuffer.array(); 这里有坑,直接取array 反序列化不出来,之前一直以为是文件存取的问题,后来发现需要使用buf.get(array);的方式,下面是正确的方式
ByteBuffer buf = byteBuffer;
byte[] array = new byte[buf.remaining()];
buf.get(array);
try {
String filePath = getFilesDir() + filename;
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(array);
} catch (IOException e) {
e.printStackTrace();
}
}
读取flatbuffers文件
try {
File file = new File(getFilesDir() + "/xml_flatbuffer");
RandomAccessFile f = new RandomAccessFile(file, "r");
byte[] data = new byte[(int) f.length()];
f.readFully(data);
ByteBuffer bb = ByteBuffer.wrap(data);
Menu rootAsMenu1 = Menu.getRootAsMenu(bb);//反序列化结束
f.close();
} catch (IOException e) {
e.printStackTrace();
}