【问题标题】:Java Performance - How to write big array to disk/sdcard with high performance?Java 性能 - 如何以高性能将大数组写入磁盘/SD 卡?
【发布时间】:2011-05-27 23:10:21
【问题描述】:

在 Java 中有没有办法将一个大数组(比如整数)写入磁盘?我在 Android 上执行此操作,但尚未找到任何接近原生 C 代码的方法。

生成的文件不需要移植到具有不同表示形式的不同机器上,因此从逻辑上讲,只需批量写入底层字节就足够了。但我不知道如何通过 Java 有效地做到这一点。

我已经尝试在网上搜索,并测试了以下内容:

  • 序列化 - 非常慢,正如预期的那样。
  • 使用 NIO - 仍然很慢 - Android 跟踪显示每个整数一次一个操作:

提前致谢


蔚来代码:

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();

【问题讨论】:

标签: java android performance io


【解决方案1】:

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

以下使用 NIO,写入需要 665 毫秒,在工作站上需要 62 毫秒。读取和写入会随机移动相同数量的数据,但读取可以从 OS 缓存中获取数据,这与写入磁盘所需的时间不同。

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.");

我已将长度添加到文件的开头,因此不必假设它。顺便说一句:我已经修复了写入中的一个错误。

【讨论】:

  • 如上所述,这确实有所帮助。现在我需要一个阅读解决方案。 Tks
  • 读取还是很慢。对于整个程序:R/W:0.70/.05(平均)。对于随机,R/W:0.05/.000。读取时间全部计入 .get
  • 一定是你的JVM或者你的硬件。在我的机器上读取需要 46 毫秒,获取需要 26 毫秒。那是 1.5 GB/s 的传输速度,相当不错。您可能需要尝试不同的方法来查看对您的 JVM 最有效的方法。我可以向你保证,这只是我的 JVM 上的一个本地方法调用,没有 java 循环。一种解决方案可能是使用内存映射的 IntBuffer 就地而不将其复制到 int[] 在我的情况下,这不会节省太多,但它可能会在您的系统上。
  • 我的 JVM 是 Android Dalvik,它确实放入了循环。我决定使用 JNI 和 C,因为这似乎是快速完成此操作的唯一方法。谢谢。
【解决方案2】:

彼得,

当某件事看起来好得令人难以置信时,通常是这样。写入 40MB 数据需要 89 毫秒,这表明您的 HDD 具有更大的 500MB/秒的带宽(因为您还包括了打开和关闭文件的时间)。这不太可能是真的。您是否检查过该文件实际上大小为 40MB。另外,我建议您初始化缓冲区以查看文件内容并非全为零。可能只是跳过了未触及的缓冲区。不管是什么,你所拥有的数字都好得令人难以置信。

谢谢。

【讨论】:

  • 顺便说一句,在您的代码中,我认为翻转是问题所在。如果你删除它,你可能会看到真正的输出。我打赌,你的输出文件是 0 字节。
【解决方案3】:

考虑缓冲你的输出流

【讨论】:

    【解决方案4】:

    我不知道 Android 的实现,但在标准 Java 中,好的老式 IO 往往胜过 NIO。

    例如,如果你有一个字节数组,我相信下面的代码应该会比较快:

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

    请记住,这将阻塞直到整个字节数组被写入。但是你没有说非阻塞行为是否有问题。

    您没有提到的另一件事是您打算在写入文件时如何对整数进行编码。您需要在写入文件之前在内存中执行编码,但可能数组太大而无法一次全部编码,在这种情况下您可以编码/写入数百 K 的块。

    【讨论】:

    • 移动字节没有问题。问题是使用字节以外的类型,即使它们是正确的二进制形式,最终也会一次一个元素转换为 java 形式(即,转换实际上什么都不做)。 NIO 解决方案避免了写入,但我不知道如何进行读取。
    • 如果您愿意阅读整个答案,我相信我已经回答了这个问题。
    • 不,我关心的是如何快速读取/写入整数数组。我说过格式不是问题 - 即简单转储到底层数组的文件/从底层数组的文件转储就足够了。但是,我无法让 Java 做到这一点。我可以用 Java 数组在 C 中完成,它工作得很好而且速度很快(见上面的 cmets)
    猜你喜欢
    • 2010-11-01
    • 2018-05-31
    • 2010-09-30
    • 2019-01-06
    • 1970-01-01
    • 2016-01-31
    • 2012-10-29
    • 2017-02-28
    • 2014-04-26
    相关资源
    最近更新 更多