Android反序列化方案

xml解析、Gson、protobuf、flatbuffers具体实践对比

情境

Android大文件反序列化

性能简单对比

解析类型 文件大小(样本数据量一致) 反序列化时间具体 解析类型 传送门
xml解析 1.8M 159ms sax/pull解析 https://developer.android.com/reference/javax/xml/parsers/SAXParserFactoryhttps://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

解析性能

image.png

序列化之空间开销:

image.png
解析类型 支持文件类型 compiler 生成命令 依赖
ProtoBuffer proto https://github.com/protocolbuffers/protobuf#protocol-compiler-installation protoc -I=SRC_DIR --java_out=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

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

推荐阅读更多精彩内容