关于对象序列化

本文探讨了在Android应用中处理大规模数据时如何更有效地进行持久化操作,对比了使用SharedPreferences与SQLite数据库的局限性,并提出了一种利用序列化技术将内存中的结构体数据持久化到磁盘的方法,通过引入BufferedOutputStream实现批量写入以减少文件写入次数,显著提高了性能。实验证明,这种方法相较于传统的SharedPreferences方式,性能提升高达77.2%,展示了在处理大量数据时如何优化存储策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开发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;
	}

	@Override
	protected 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;
	}

	@Override
	protected 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里:





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值