问题
遇到一个配置文件,里面有近10000个double类型的数字,每个数字1行,加载到内存里变成List<Double>
,在小米3上加载时间需要200多ms。这种写法可读性好,但牺牲了速度和文件大小。而这个文件的使用场景则是越快起好,使用文本方式储存double数组就显得非常naive了。
文件内容类似下面这样1
2
3
4
5
6-0.07442092964745924
-0.2802236054615799
0
-9.125085940770578e-018
0.264550123930026
......
解决方案
先Google了一下,StackOverflow上有人问了这个问题,答案是在DataOutputStream
, ObjectOutputStream
, FileChannel
三者之间FileChannel最快,因此直接上FileChannel。
研究了一下Java的NIO(FileChannel
, DoubleBuffer
等)。使用内存映射重写了加载的逻辑。重写后加载时间只需要2ms了,速度快了100倍。文件大小也减小了约 60%。
经过研究发现StackOverflow的回答者给的代码还有很大优化空间,写int数组时使用提for循环,一个一个写,其实可以使用IntBuffer
一次性写进去,这个速度应该快很多。
下面是写入文件的代码,使用了Magic Number 技巧。实际使用中可以再增加版本号等其他 header 字段做进一步的校验,方便以后扩展文件格式。
1 | private static final int INT_SIZE = Integer.SIZE / Byte.SIZE; |
读取的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25private static double[] readFileByChannel(File inputFile) {
FileInputStream fis = null;
try {
fis = new FileInputStream(inputFile);
FileChannel channel = fis.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
int magicNumber = buffer.getInt();
if (magicNumber != MAGIC_NUMBER) {
Log.e(TAG, "wrong file format, magic=" + Integer.toHexString(magicNumber));
return null;
}
int size = buffer.getInt();
double[] doubles = new double[size];
buffer.asDoubleBuffer().get(doubles);
channel.close();
return doubles;
} catch (IOException e) {
e.printStackTrace();
} finally {
closeQuietly(fis);
}
return null;
}
Note:
需要注意的一点是,验证修改前后两种方法读取的数据是否相同时,比较符点数是否相等,不能使用 ==
,而应该使用 Double.compare(double double1, double double2)
及Float.compare(float float1, float float2)
。
Android兼容性:
FileChannel
, MappedByteBuffer
, FileChannel.map()
,DoubleBuffer
, Double.compare
这些API从API Level 1就有的,可以放心使用。(但NIO2的API则无法使用。)