代码改变世界

android 如何优化数据存储

2019-06-03 15:20  般若Android  阅读(475)  评论(0编辑  收藏  举报

  将特定结构的数据转化为一种能被记录和还原的格式即存储

  数据存储的六个关键因素:正确性、时间开销、空间开销、安全、开发成本和兼容性。我们不可能同时把所有要素都做到最好,所谓数据存储优化就是根据自己的使用场景去把其中的一项或者几项做到最好。

  更宽泛的讲,存储不一定就是将数据存放在磁盘中,比如放到内存中、通过网络传输也算是存储的一种形式。或者我们也可以把这个过程叫做对象或者数据的序列化。

对于大部分的开发者来说,我们不一定有精力去创造一种数据序列化的格式,Android常用的序列化方法如何进行选择。

  对象的序列化

  应用程序中的对象存储在内存中,如果我们想把对象存储下来或者在网络上传输,这个时候就需要用到对象的序列化和反序列化。

  对象序列化就是把一个Object对象所有的信息都表示成一个字节序列,包括Class信息、继承关系信息、访问权限、变量类型以及数值信息等。

  1.Serializable

  Serializable是java原生的序列化机制,在Android中也有被广泛使用,我们可以通过Serializable将对象持久化存储,也可通过Bundle传递Serializable序列化数据。

  Serializable的原理

  Serializable原理是通过ObjectInputStream和ObjectOutputStream来实现的,我们以Android6.0源码为例,部分ObjectOutputStream的源码实现

  

  整个序列化过程使用了大量的反射和临时变量,而且在序列化对象的时候,不仅会序列化当前对象本身,还需要递归序列化对象引用的其他对象

  整个过程计算非常复杂,而且因为存在大量反射和GC的影响,序列化的性能会比较差。另外一方面因为序列化文件需要包含的信息非常多,导致它的大小比Class文件本身还要大很多,这样会导致I/O读写的性能问题。

  Serializable的进阶

  既然Serializable性能那么差,那它有什么优势吗

  1、writeObject和readObject方法。Serializable序列化支持替代默认流程,它会先反射判断是否存在我们事先的序列化方法writeObject或反序列化方法readObject,通过这两个方法,我们可以对某些字段做一些特殊修改,也可以实现序列化的加密功能。

  2.writeReplace和readResolve方法。这两个方法代理序列化的对象,可以实现自定义返回的序列化实例。我们可以通过它们实现对象序列化的版本兼容,例如通过readResolve方法可以把老版本的序列化对象转换成新版本的对象类型。

  Serializable的序列化与反序列化调用流程如下

 

  Serializable的注意事项

  Serializable虽然使用非常简单,但是也有一定需要注意的事项选段

  1.不被序列化字段。类的static变量以及被声明为transient的字段,默认的序列化机制都会忽略该字段,不会进行序列化存储。

  2.SerialVersionUID 在类实现Serializable接口后,我们需要添加一个SerialVersionID,它相当于类的版本号,这个ID我们可以显式声明也可以让编译器自己计算。通常我建议显示声明会更加稳妥,因为显式声明假如类发生一点点变化,进行反序列化都会由于serialVersionUID改变而导致InvalidClassException异常

  3.构造方法。Serializable的反序列默认是不会执行构造函数的,它是根据数据流中对Object的描述信息创建对象的,如果一些逻辑依赖构造函数,就可能出现问题,例如一个静态变量只在构造函数中赋值,当然我们也可以通过进阶方法做自定义的反序列化修改

  Parcelable

  由于Java的Serializable的性能较低,Android需要重新设计一套更加轻量且高效的对象序列化和反序列化机制,Parcelable正是在这个背景下产生的,它核心的作用就是为了解决Android中跨进程通信的性能问题。

  Parcelable只会在内存中进行序列化操作,并不会将数据存储在磁盘里

  当然可以通过marshall接口获取byte数组,然后存在文件中从而实现Parcelable的永久存储。

  Parcelable的注意事项

  在时间开销和使用成本的权衡上,Parcelable机制选择的是性能优先

  所以它在写入和读取的时候都需要手动添加自定义代码,使用起来相比Serializable会复杂很多,但是正因为这样,Parcelable才不需要采取反射的方式去实现序列化和反序列化。

  虽然可以通过取巧的方法实现Parcelable的永久存储,但是它存在两个问题

  1.系统版本的兼容性。由于Parcelable设计本意是在内存中使用,我们无法保证Android版本Parcel.cpp实现都完全一致,如果不同版本实现有所差异,或者有厂商修改了实现,可能存在问题

  2.数据前后兼容性。Parcelablel并没有版本管理的设计,如果我们类的版本出现升级,写入的顺序以及字段类型的兼容都需要格外注意,这也带来了很大的维护成本

  一般来说,如果需要持久化存储的话,一般还是不得不选择性能更差的Serializable方案

  Serial

  三方性能更优

  数据的序列化

  对象的序列化要记录的信息还是比较多,在操作比较频繁的时候,对应用的影响还是不少的,这个时候我们可以选择使用数据的序列化

  1.JSON

  JSON是一种轻量级的数据交互格式,它被广泛使用在网络传输中,很多应用于服务端的通信都是使用JSON格式进行交互

  优势

  1.相比对象序列化方案,速度更快,体积更小

  2.相比二进制的序列化方案,结果可读,易于排查问题

  3.使用方便、支持跨平台、跨语言、支持嵌套引用

  2.Protocal Buffers

  相比对象序列化方案,JSON的速度更快、体积更小,不过为了保证JSON的中间结果是可读的,它并没有做二进制的压缩,也因此JSON的性能还没有达到极致。

  如果应用的数据量非常大,又或者对性能有更高的要求,此时Protocal Buffers是一个非常好的选择,它是Google开源的跨语言编码协议,Google内部的几乎所有RPC都在使用这个协议  

  存储监控

  随后补上