【问题标题】:Fastest way to write an array of integers to a file in Java?用Java将整数数组写入文件的最快方法?
【发布时间】:2010-12-05 12:53:22
【问题描述】:

正如标题所说,我正在寻找将整数数组写入文件的最快方法。数组的大小会有所不同,实际上包含 2500 到 25 000 000 个整数。

这是我目前使用的代码:

DataOutputStream writer = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));

for (int d : data)
  writer.writeInt(d);

鉴于 DataOutputStream 具有写入字节数组的方法,我尝试将 int 数组转换为字节数组,如下所示:

private static byte[] integersToBytes(int[] values) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    for (int i = 0; i < values.length; ++i) {
        dos.writeInt(values[i]);
    }

    return baos.toByteArray();
}

像这样:

private static byte[] integersToBytes2(int[] src) {
    int srcLength = src.length;
    byte[] dst = new byte[srcLength << 2];

    for (int i = 0; i < srcLength; i++) {
        int x = src[i];
        int j = i << 2;
        dst[j++] = (byte) ((x >>> 0) & 0xff);
        dst[j++] = (byte) ((x >>> 8) & 0xff);
        dst[j++] = (byte) ((x >>> 16) & 0xff);
        dst[j++] = (byte) ((x >>> 24) & 0xff);
    }
    return dst;
}

两者似乎都略微提高了速度,大约 5%。我没有对它们进行足够严格的测试来确认这一点。

是否有任何技术可以加快此文件写入操作,或 Java IO 写入性能最佳实践的相关指南?

【问题讨论】:

  • 您希望文件内容的格式准确吗?
  • 自己内联代码将使尚未热身的代码更快。但是,如果您运行 5-10 秒的测试,您将看到这是否有真正的改进。 (因为他的 JVM 会为你做这件事)
  • @Karl 只是一个没有格式化的整数序列。

标签: java performance file-io


【解决方案1】:

我看了三个选项:

  1. 使用DataOutputStream;
  2. 使用ObjectOutputStream(对于Serializable对象,int[]是);和
  3. 使用FileChannel

结果

DataOutputStream wrote 1,000,000 ints in 3,159.716 ms
ObjectOutputStream wrote 1,000,000 ints in 295.602 ms
FileChannel wrote 1,000,000 ints in 110.094 ms

所以 NIO 版本是最快的。它还具有允许编辑的优点,这意味着您可以轻松更改一个 int,而 ObjectOutputStream 则需要读取整个数组,对其进行修改并将其写入文件。

代码如下:

private static final int NUM_INTS = 1000000;

interface IntWriter {
  void write(int[] ints);
}

public static void main(String[] args) {
  int[] ints = new int[NUM_INTS];
  Random r = new Random();
  for (int i=0; i<NUM_INTS; i++) {
    ints[i] = r.nextInt();
  }
  time("DataOutputStream", new IntWriter() {
    public void write(int[] ints) {
      storeDO(ints);
    }
  }, ints);
  time("ObjectOutputStream", new IntWriter() {
    public void write(int[] ints) {
      storeOO(ints);
    }
  }, ints);
  time("FileChannel", new IntWriter() {
    public void write(int[] ints) {
      storeFC(ints);
    }
  }, ints);
}

private static void time(String name, IntWriter writer, int[] ints) {
  long start = System.nanoTime();
  writer.write(ints);
  long end = System.nanoTime();
  double ms = (end - start) / 1000000d;
  System.out.printf("%s wrote %,d ints in %,.3f ms%n", name, ints.length, ms);
}

private static void storeOO(int[] ints) {
  ObjectOutputStream out = null;
  try {
    out = new ObjectOutputStream(new FileOutputStream("object.out"));
    out.writeObject(ints);
  } catch (IOException e) {
    throw new RuntimeException(e);
  } finally {
    safeClose(out);
  }
}

private static void storeDO(int[] ints) {
  DataOutputStream out = null;
  try {
    out = new DataOutputStream(new FileOutputStream("data.out"));
    for (int anInt : ints) {
      out.write(anInt);
    }
  } catch (IOException e) {
    throw new RuntimeException(e);
  } finally {
    safeClose(out);
  }
}

private static void storeFC(int[] ints) {
  FileOutputStream out = null;
  try {
    out = new FileOutputStream("fc.out");
    FileChannel file = out.getChannel();
    ByteBuffer buf = file.map(FileChannel.MapMode.READ_WRITE, 0, 4 * ints.length);
    for (int i : ints) {
      buf.putInt(i);
    }
    file.close();
  } catch (IOException e) {
    throw new RuntimeException(e);
  } finally {
    safeClose(out);
  }
}

private static void safeClose(OutputStream out) {
  try {
    if (out != null) {
      out.close();
    }
  } catch (IOException e) {
    // do nothing
  }
}

【讨论】:

  • 很好的测试,但 FileChannel 出现错误:java.nio.channels.NonReadableChannelException。你知道为什么吗?
  • 我用@dacwe的方法写入FileChannel,修改代码在这里pastebin.com/HhpcS7HX
  • 我得到了同样的例外,为什么有人知道?
  • 问题是代码试图从只写对象读取和写入; FileOutputStream 仅支持写入。相反,代码应该使用以“rw”打开的 RandomAccessFile。
  • 另一个小错误是 out.write(anInt);在 DataOutputStream 中写入一个字节,而不是整数。使用整数时,性能可能会更差。另一方面,您应该将 FileOutputStream 包装在 BufferedOutputStream 中。
【解决方案2】:

我会使用 nio 包中的FileChannelByteBuffer。这种方法似乎(在我的计算机上)可以提供 2 到 4 倍的写入性能

程序输出:

normal time: 2555
faster time: 765

这是程序:

public class Test {

    public static void main(String[] args) throws IOException {

        // create a test buffer
        ByteBuffer buffer = createBuffer();

        long start = System.currentTimeMillis();
        {
            // do the first test (the normal way of writing files)
            normalToFile(new File("first"), buffer.asIntBuffer());
        }
        long middle = System.currentTimeMillis(); 
        {
            // use the faster nio stuff
            fasterToFile(new File("second"), buffer);
        }
        long done = System.currentTimeMillis();

        // print the result
        System.out.println("normal time: " + (middle - start));
        System.out.println("faster time: " + (done - middle));
    }

    private static void fasterToFile(File file, ByteBuffer buffer) 
    throws IOException {

        FileChannel fc = null;

        try {

            fc = new FileOutputStream(file).getChannel();
            fc.write(buffer);

        } finally {

            if (fc != null)
                fc.close();

            buffer.rewind();
        }
    }

    private static void normalToFile(File file, IntBuffer buffer) 
    throws IOException {

        DataOutputStream writer = null;

        try {
            writer = 
                new DataOutputStream(new BufferedOutputStream(
                        new FileOutputStream(file)));

            while (buffer.hasRemaining())
                writer.writeInt(buffer.get());

        } finally {
            if (writer != null)
                writer.close();

            buffer.rewind();
        }
    }

    private static ByteBuffer createBuffer() {
        ByteBuffer buffer = ByteBuffer.allocate(4 * 25000000);
        Random r = new Random(1);

        while (buffer.hasRemaining()) 
            buffer.putInt(r.nextInt());

        buffer.rewind();

        return buffer;
    }
}

【讨论】:

  • 您可以使用直接内存缓冲区重新测试吗?这应该会使写入速度更快(因为它必须复制到直接缓冲区)
  • 也尝试使用 64K 缓冲区大小的 BufferOutputStream
  • 谢谢,FileChannel 方法要快得多。
【解决方案3】:

基准测试应该每隔一段时间重复一次,不是吗? :) 在修复了一些错误并添加了我自己的写作变体之后,这里是 在华硕 ZenBook UX305 上运行基准测试时得到的结果 运行 Windows 10(时间以秒为单位):

Running tests... 0 1 2
Buffered DataOutputStream           8,14      8,46      8,30
FileChannel alt2                    1,55      1,18      1,12
ObjectOutputStream                  9,60     10,41     11,68
FileChannel                         1,49      1,20      1,21
FileChannel alt                     5,49      4,58      4,66

这是在同一台计算机上运行但使用 Arch 的结果 Linux和write方法的顺序切换:

Running tests... 0 1 2
Buffered DataOutputStream          31,16      6,29      7,26
FileChannel                         1,07      0,83      0,82
FileChannel alt2                    1,25      1,71      1,42
ObjectOutputStream                  3,47      5,39      4,40
FileChannel alt                     2,70      3,27      3,46

每个测试都写了一个 800mb 的文件。无缓冲的 DataOutputStream 占用了 太长了,所以我将它排除在基准之外。

正如所见,使用文件通道写入仍然胜过所有垃圾 其他方法,但是字节缓冲区是否是很重要的 内存映射与否。没有内存映射文件通道写入 耗时 3-5 秒:

var bb = ByteBuffer.allocate(4 * ints.length);
for (int i : ints)
    bb.putInt(i);
bb.flip();
try (var fc = new FileOutputStream("fcalt.out").getChannel()) {
    fc.write(bb);
}

使用内存映射,时间减少到 0.8 到 1.5 之间 秒:

try (var fc = new RandomAccessFile("fcalt2.out", "rw").getChannel()) {
    var bb = fc.map(READ_WRITE, 0, 4 * ints.length);
    bb.asIntBuffer().put(ints);
}

但请注意,结果取决于顺序。尤其如此 Linux。看来内存映射方法没有写 完整数据,而是将作业请求卸载到操作系统并返回 在完成之前。这种行为是否可取 视情况而定。

内存映射也可能导致 OutOfMemory 问题,因此不是 始终是正确的工具 采用。 Prevent OutOfMemory when using java.nio.MappedByteBuffer.

这是我的基准代码版本: https://gist.github.com/bjourne/53b7eabc6edea27ffb042e7816b7830b

【讨论】:

    【解决方案4】:

    我认为您应该考虑使用文件通道(java.nio 库)而不是普通流(java.io)。一个很好的起点是这个有趣的讨论:Java NIO FileChannel versus FileOutputstream performance / usefulness

    以及下面的相关cmets。

    干杯!

    【讨论】:

      【解决方案5】:

      编写 int[] 的主要改进是:

      • 增加缓冲区大小。大小适合大多数流,但使用更大的缓冲区可以更快地访问文件。这可能会产生 10-20% 的改进。

      • 使用 NIO 和直接缓冲区。这允许您在不转换为字节的情况下写入 32 位值。这可能会产生 5% 的改进。

      顺便说一句:您应该能够每秒写入至少 1000 万个 int 值。使用磁盘缓存可以将其增加到每秒 2 亿次。

      【讨论】:

        【解决方案6】:

        数组是可序列化的 - 你不能只使用writer.writeObject(data);吗?这肯定会比单独的writeInt 调用更快。

        如果您对输出数据格式有其他要求,而不是检索到 int[],这是一个不同的问题。

        【讨论】:

        • writeObject 开销很大,最后使用 writeInt。这是一种非常友好的对象编写方式,我怀疑在大多数情况下是更好的选择。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-06
        • 2010-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多