FlatBuffers是Google开源的一个跨平台的、高效的、提供了C++/Java接口的序列化工具库,它是Google专门为游戏开发或其他性能敏感的应用程序需求而创建。尤其适用移动,嵌入式平台,这些平台在内存大小及带宽相比桌面系统都是受限的,而应用程序比如游戏又有更高的性能要求。它将序列化数据存储在缓存中,这些数据既可以存储在文件中,又可以通过网络原样传输,而不需要任何解析开销。以下是项目地址: 代码托管主页:https://github.com/google/flatbuffers; 项目介绍主页:http://google.github.io/flatbuffers/index.html;
相比传统的JSON和Protocol Buffers等序列化工具,FlatBuffers具有如下的一些优点:
Protocol Buffers的确和FlatBuffers比较类似,但其主要区别在于FlatBuffers在访问数据前不需要解析/拆包这一步,而且Protocol Buffers既没有可选的文本导入/导出功能,也没有Schemas语法特性(比如union)。
JSON是一种轻量级的数据交换格式,JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步应用程序中将字符串从 Web 客户机传递给服务器端程序。JSON和动态类型语言(如JavaScript)一起使用时非常方便。然而在静态类型语言中序列化数据时,JSON不但具有运行效率低的明显缺点,而且会让你写更多的代码来访问数据。
首先来看一下FlatBuffers项目为开发者提供了哪些内容,可以从官网下载源码,其目录结构如下图:
如果要将FlatBuffers 用到我们的项目中,又需要哪些流程呢?可以参考下面的流程图:
就像Parcel和Serializable的序列化一样,FlatBuffers的是使用方式上也比最传统的JSON序列化要复杂的多。在实际上面开发中,为了降低开发的难度,提高开发效率,我们会将源码编译成可植入的第三方库。下面以Java环境为例,来介绍FlatBuffers的简单使用方法。读者可以到对应的maven仓库下载。
现在,假如我们拿到的json文件的格式是下面这样的:
{
"repos": [
{
"id": 27149168,
"name": "acai",
"full_name": "google/acai",
"owner": {
"login": "google",
"id": 1342004,
...
"type": "Organization",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/google/acai",
"description": "Testing library for JUnit4 and Guice.",
...
"watchers": 21,
"default_branch": "master"
},
...
]
}
注:可以通过下面的链接来获取更完整的json对象
我们需要准备一个model文件,它定义了我们想要序列化/反序列化 的数据结构,这个模式将被flatc用于创建Java模型以及从JSON到FlatBuffer二进制文件的转换。
现在,我们所要做的所有事情就是创建3个表:ReposList,Repo和User,并定义root_type。例如:
table ReposList {
repos : [Repo];
}
table Repo {
id : long;
name : string;
full_name : string;
owner : User;
//...
labels_url : string (deprecated);
releases_url : string (deprecated);
}
table User {
login : string;
id : long;
avatar_url : string;
gravatar_id : string;
//...
site_admin : bool;
}
root_type ReposList;
注:完整的模式文件可以点击下面的链接来获取
接下来,我们所需要做的就是将repos_json.json转换为FlatBuffers二进制文件,并产生Java模型,其可以以Java友好的方式表示我们的数据,下面是转换的命令:
$ ./flatc -j -b repos_schema.fbs repos_json.json
如果没有任何报错,将会生成如下4个文件:
repos_json.bin (将被重命名为repos_flat.bin)
Repos/Repo.java
Repos/ReposList.java
Repos/User.java
接下来,我们可以使用FlatBuffers提供的Java库来处理在Java中直接处理这种数据格式,此处使用需要使用到 flatbuffers-java-1.2.0-SNAPSHOT.jar。
public class MainActivity extends AppCompatActivity {
@Bind(R.id.tvFlat)
TextView tvFlat;
@Bind(R.id.tvJson)
TextView tvJson;
private RawDataReader rawDataReader;
private ReposListJson reposListJson;
private ReposList reposListFlat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
rawDataReader = new RawDataReader(this);
}
@OnClick(R.id.btnJson)
public void onJsonClick() {
rawDataReader.loadJsonString(R.raw.repos_json).subscribe(new SimpleObserver<String>() {
@Override
public void onNext(String reposStr) {
parseReposListJson(reposStr);
}
});
}
private void parseReposListJson(String reposStr) {
long startTime = System.currentTimeMillis();
reposListJson = new Gson().fromJson(reposStr, ReposListJson.class);
for (int i = 0; i < reposListJson.repos.size(); i++) {
RepoJson repo = reposListJson.repos.get(i);
Log.d("FlatBuffers", "Repo #" + i + ", id: " + repo.id);
}
long endTime = System.currentTimeMillis() - startTime;
tvJson.setText("Elements: " + reposListJson.repos.size() + ": load time: " + endTime + "ms");
}
@OnClick(R.id.btnFlatBuffers)
public void onFlatBuffersClick() {
rawDataReader.loadBytes(R.raw.repos_flat).subscribe(new SimpleObserver<byte[]>() {
@Override
public void onNext(byte[] bytes) {
loadFlatBuffer(bytes);
}
});
}
private void loadFlatBuffer(byte[] bytes) {
long startTime = System.currentTimeMillis();
ByteBuffer bb = ByteBuffer.wrap(bytes);
reposListFlat = frogermcs.io.flatbuffs.model.flat.ReposList.getRootAsReposList(bb);
for (int i = 0; i < reposListFlat.reposLength(); i++) {
Repo repos = reposListFlat.repos(i);
Log.d("FlatBuffers", "Repo #" + i + ", id: " + repos.id());
}
long endTime = System.currentTimeMillis() - startTime;
tvFlat.setText("Elements: " + reposListFlat.reposLength() + ": load time: " + endTime + "ms");
}
}
在上面的示例代码中,有两个方法是比较核心的,需要我们注意。
下面我们来测试下FlatBuffers和传统的json在数据解析上的耗时,我们以4mb的JSON文件为例。
如图,可以发现FlatBuffers花了1-5ms,JSON花了大约2000ms。并且FlatBuffers期间Android App中没有GC,而在使用JSON时发生了很多次GC,测试的源码可以通过以下地址下载:FlatBuffers耗时测试