Java性能 - 如何以高性能将大数组写入磁盘/SD卡?

4
在Java中有没有一种方法可以将一个大的整数数组写入磁盘?我正在Android上进行此操作,并且没有找到与本地C代码相近的方法。生成的文件不需要在具有不同表示的不同机器之间可移植,因此逻辑上只需要对基础字节进行批量写入即可。但是我不知道如何从Java有效地实现它。我尝试过搜索网络,并测试了以下内容:序列化-非常慢,正如预期的那样。使用NIO-仍然很慢- Android跟踪显示每个整数一次操作:提前感谢。NIO代码:
int[] array = new array[10000000];

...

raf = new RandomAccessFile(ti.testFileName, "rw");
chan = raf.getChannel();
MappedByteBuffer out = chan.map(FileChannel.MapMode.READ_WRITE, 0, array.length*4);
ib = out.asIntBuffer();
ib.put(array);
out.force();
raf.close();

如果这是Android而不是Java,你的标签不应该反映出来吗? - Peter Lawrey
https://dev59.com/iHNA5IYBdhLWcg3wL6oc - jmj
1
@org.life.java 这个链接讨论了写文本而不是二进制,这不会是更快的解决方案。 - Peter Lawrey
@Peter 这些链接对 OP 很有用,因为他想要将一个数组写入磁盘,他可以从这些链接中获得基本的想法。 - jmj
在闪存上写入40MB的整数将需要很长时间,因为闪存本身速度慢,且不可预测地慢(例如磨损均衡)。 - CommonsWare
显示剩余3条评论
4个回答

3

您说速度很慢,但速度可能取决于磁盘子系统的速度。您应该能够在大约半秒钟内将40 MB写入常规磁盘以提交到磁盘。

以下使用NIO,需要665毫秒进行写入,在工作站上需要62毫秒。读取和写入会将相同数量的数据移动,但读取可以从操作系统缓存中获取其数据,区别在于写入到磁盘所需的时间。

int[] ints = new int[10 * 1000 * 1000];
long start = System.nanoTime();

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(ints.length*4+4);
byteBuffer.putInt(ints.length);
IntBuffer intBuffer = byteBuffer.asIntBuffer();
intBuffer.put(ints);
byteBuffer.position(0);

FileChannel fc = new FileOutputStream("main.dat").getChannel();
fc.write(byteBuffer);
fc.force(false);
fc.close();
long time = System.nanoTime() - start;
System.out.println("Write time " + time / 1000 / 1000 + " ms.");

long start2 = System.nanoTime();
FileChannel fc2 = new FileInputStream("main.dat").getChannel();
ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
while(lengthBuffer.remaining()>0) fc2.read(lengthBuffer);
int length = lengthBuffer.getInt(0);

int[] ints2 = new int[length];
ByteBuffer buffer2 = ByteBuffer.allocateDirect(length*4);
while(buffer2.remaining()>0 && fc2.read(buffer2) > 0);
buffer2.flip();
buffer2.asIntBuffer().get(ints2);
long time2 = System.nanoTime() - start2;
System.out.println("Read time " + time2 / 1000 / 1000 + " ms.");

我已经在文件开头添加了长度,这样就不需要假设了。顺便说一句:写入时存在一个错误,我已经修复了。


如上所述,这确实有所帮助。现在我需要一个读取解决方案。谢谢。 - Mesocyclone
读取速度仍然非常慢。对于整个程序而言:读/写:0.70 / 0.05(平均)。对于随机读取,读/写:0.05 / 0.000。读取时间全部用在了 .get 上。 - Mesocyclone
这可能是你的JVM或硬件问题。在我的机器上,读取需要46毫秒,获取需要26毫秒。这是1.5 GB/s的传输速度,相当不错。你可能需要尝试不同的方法来确定对你的JVM最有效的方式。我可以向你保证,在我的JVM上,这只是一个本地方法调用,没有Java循环。一个解决方案可能是使用内存映射的IntBuffer而不是将其复制到int[]中。在我的情况下,这并不能节省多少空间,但在你的系统上可能会有所帮助。 - Peter Lawrey
1
我的JVM是Android Dalvik,它会放入循环中。我已经决定使用JNI和C,因为这似乎是唯一快速解决的方法。谢谢。 - Mesocyclone

1

我对 Android 的实现一无所知,但在标准 Java 中,老式的 IO 常常比 NIO 更高效。

例如,如果您有一个字节数组,我相信以下代码应该相对快速:

byte[] bytes = new byte[10000];
// ...
FileOutputStream out = new FileOutputStream(...);
try {
    out.write(bytes);
} finally {
    out.close();
}

请记住,这将阻塞直到整个字节数组被写入。但是您没有说明非阻塞行为是否是一个问题。
另一件事情是您没有提及的是在写入文件时如何编码整数。您需要在内存中执行编码,然后再写入文件,但可能数组太大而无法一次性编码所有内容,在这种情况下,您可以分块编码/写入几百K的数据块。

1
移动字节没有问题。问题在于使用除字节以外的类型,即使它们处于正确的二进制形式,最终也会被逐个元素地转换为Java形式(即实际上转换不起作用)。NIO解决方案可以避免这种情况发生在写入时,但我不知道如何在读取时做到这一点。 - Mesocyclone
如果您愿意阅读整个答案,我相信我已经回答了这个问题。 - Neil Bartlett
不,我的整个关注点是如何快速读写整数数组。我说过格式不是问题——即底层数组的简单转储到/从文件中就足够了。然而,我无法让Java做到这一点。我可以在C中使用Java数组来完成这个任务,它运行良好且速度很快(请参见上面的评论)。 - Mesocyclone

0

Peter,

当某些事情看起来太好以至于不可思议时,通常情况下它确实是如此。89毫秒写入40MB的数据表明您的硬盘驱动器带宽要远大于500MB/秒(因为您还包括打开和关闭文件的时间)。这似乎不太可能。您是否检查了文件确实是40MB大小?另外,我建议您初始化缓冲区以查看文件内容是否全为零。也许一个未被触及的缓冲区只是被跳过了。无论出现了什么问题,您所得到的数字都太好以至于难以置信。

谢谢。


顺便提一下,在你的代码中,我认为翻转是问题所在。如果你将其删除,也许就能看到真正的输出了。我敢打赌,现在的情况是,你的输出文件大小为0字节。 - Virtually Real

0

考虑对输出流进行缓冲处理


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接