关于对象序列化

来源:互联网 发布:2013知乎年度吐槽精选 编辑:程序博客网 时间:2024/05/23 00:10

在开发Android应用时,我们经常需要将数据进行持久化。对于少量的数据,Android提供了轻量的,以XML格式文件保存的SharedPreferences工具。对于大量的,且需要进行增删改查操作的数据,Android则提供了SQLite数据库。有时,我们希望对内存里的某些结构体数据(比如某个类的实例、ArrayList等)进行持久化,这时,使用SharedPreferences则过于繁琐,而使用数据库则太“大材小用”,其实我们可以直接将其序列化到磁盘上,等到要用时再反序列化。如下面的demo,我们将size为1000的ArrayList<Data>用ObjectOutputStream序列化到磁盘(作为demo,为了简单,直接将IO操作放到主线程,下同):

public class MainActivity extends Activity {public static class Data implements Serializable {private static final long serialVersionUID = 1L;public String str1 = "aa";public String str2 = "bb";public String str3 = "cc";public int int1 = 1;public int int2 = 2;public int int3 = 3;public long long1 = 1L;public long long2 = 2L;public long long3 = 3L;public InnerStruct is = new InnerStruct();}public static class InnerStruct implements Serializable {private static final long serialVersionUID = 1L;public float f = 1.0f;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);FileOutputStream fos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream(getApplication().getFilesDir().getAbsolutePath() + "/test.bin");oos = new ObjectOutputStream(fos);ArrayList<Data> data = new ArrayList<Data>();for (int i = 0; i < 1000; i++) {data.add(new Data());}oos.writeObject(data);} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (oos != null) {try {oos.close();} catch (IOException e) {e.printStackTrace();}}if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}}

运行程序,我们可以看到test.bin文件被正确地写到了/data/data/com.example.iotest/files目录下,大小为67313字节:


但是,这是新手的写法,稍微有点经验的程序员看到这些代码都会指出其IO性能问题:写文件次数过多!是吗?我们来看一下到底写了多少次文件:

这是用Traceview工具获取的数据,在解释这个数据之前,我们需要先从源码层面了解ObjectOutputStream在序列化对象时都干了哪些事情。ObjectOutputStream的主要功能是将复杂的对象进行拆解,真正写文件是由FileOutputStream的write(byte[] buffer, int byteOffset, int byteCount)方法进行,之后的调用路径如下图所示:

我们可以看到JAVA层的最后一个方法是libcore.io.Posix的writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount),之后就通过JNI调用了native的方法。libcore.io.Posix对应的C++文件是libcore_io_Posix.cpp(源码:http://androidxref.com/4.0.4/xref/libcore/luni/src/main/native/libcore_io_Posix.cpp#),writeBytes对应的C++方法是Posix_writeBytes(JNIEnv* env, jobject, jobject javaFd, jbyteArray javaBytes, jint byteOffset, jint byteCount),如下图所示:

在Posix_writeBytes方法里最后调用了GNU的libc库函数write (int filedes, const void *buffer, size_t size),这里是操作系统层面写文件的地方。

我们回过头来看看Traceview的数据,就可以发现写文件的次数达到了惊人的19052次!
那么我们应该怎样写才会减少写文件的次数呢?答案是用BufferedOutputStream做缓存,再批量写入文件,如下所示:

public class MainActivity extends Activity {public static class Data implements Serializable {private static final long serialVersionUID = 1L;public String str1 = "aa";public String str2 = "bb";public String str3 = "cc";public int int1 = 1;public int int2 = 2;public int int3 = 3;public long long1 = 1L;public long long2 = 2L;public long long3 = 3L;public InnerStruct is = new InnerStruct();}public static class InnerStruct implements Serializable {private static final long serialVersionUID = 1L;public float f = 1.0f;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);FileOutputStream fos = null;BufferedOutputStream bos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream(getApplication().getFilesDir().getAbsolutePath() + "/test.bin");bos = new BufferedOutputStream(fos);oos = new ObjectOutputStream(bos);ArrayList<Data> data = new ArrayList<Data>();for (int i = 0; i < 1000; i++) {data.add(new Data());}oos.writeObject(data);oos.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (oos != null) {try {oos.close();} catch (IOException e) {e.printStackTrace();}}if (bos != null) {try {bos.close();} catch (IOException e) {e.printStackTrace();}}if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}}

这个写法写文件的次数是多少呢?我们用Traceview来看一下:

现在就只有9次了。

我们可以对比一下这两种方法写文件的耗时,无缓存的方法和有缓存的方法各执行10次(单位:ms):

无缓存600456466483473525456470531490有缓存9812611613310010512211779132

无缓存方法执行的平均时间为:495ms,有缓存方法执行的平均时间为:112.8ms,时间减少77.2%。

BufferedOutputStream其实很简单,内部的数据结构就是一个byte数组,一个int的计数器:

其默认的buffer大小为8K:



当写文件时,如果buffer已经装不下了,就先将buffer里的数据写入文件,如果可以装下,则将数据先暂时放到buffer里:





0 0
原创粉丝点击