从二进制文件读取大量整数的最快方法

5
我在一个嵌入式Linux设备上使用Java 1.5,并希望读取一个包含2MB int值的二进制文件。(目前是4字节大端,但我可以决定格式)
使用DataInputStream通过BufferedInputStream(使用dis.readInt()),这些500,000次调用需要17秒才能读取,但将文件读入一个大的字节缓冲区只需要5秒。
如何更快地将该文件读入一个巨大的int[]中?
读取过程不应使用超过另外512 kb。
以下代码使用nio方法不比java io的readInt()方法更快。
    // asume I already know that there are now 500 000 int to read:
    int numInts = 500000;
    // here I want the result into
    int[] result = new int[numInts];
    int cnt = 0;

    RandomAccessFile aFile = new RandomAccessFile("filename", "r");
    FileChannel inChannel = aFile.getChannel();

    ByteBuffer buf = ByteBuffer.allocate(512 * 1024);

    int bytesRead = inChannel.read(buf); //read into buffer.

    while (bytesRead != -1) {

      buf.flip();  //make buffer ready for get()

      while(buf.hasRemaining() && cnt < numInts){
       // probably slow here since called 500 000 times
          result[cnt] = buf.getInt();
          cnt++;
      }

      buf.clear(); //make buffer ready for writing
      bytesRead = inChannel.read(buf);
    }


    aFile.close();
    inChannel.close();

更新:答案的评估:

在PC上,使用Memory Map和IntBuffer的方法是我设置中最快的。
在嵌入式设备上,没有jit,java.io DataInputStream.readInt()略微更快(17秒对比Memory Map和IntBuffer的20秒)

最终结论: 通过算法变更可以更容易地实现显著的加速。(初始文件更小)


请确保同时查看http://makeprogrammingyourforte.blogspot.in/2012/09/fastest-way-to-read-input-in-java.html。 - Algorithmist
@Algorithmist 我查看了你的链接,但它是从文本文件中读取的。 - AlexWien
伯克利提供了一个名为Bulk IO JNI的扩展,可以在此处获取(http://www.cs.berkeley.edu/~bonachea/java/)。我尚未使用过它,但它可能值得一试。 - Zim-Zam O'Pootertoot
目标机器是否支持多线程? - Zymus
是的,但我无法想象那应该如何提高速度; - AlexWien
3个回答

4
我不知道这种方法是否比Alexander提供的更快,但你可以尝试映射文件。
    try (FileInputStream stream = new FileInputStream(filename)) {
        FileChannel inChannel = stream.getChannel();

        ByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        int[] result = new int[500000];

        buffer.order( ByteOrder.BIG_ENDIAN );
        IntBuffer intBuffer = buffer.asIntBuffer( );
        intBuffer.get(result);
    }

2
在 PC 上它是最快的解决方案,但在没有 JIT 的嵌入式设备上它需要 20 秒,所以 Java IO 仍然是最快的。有趣... - AlexWien

3
你可以使用nio包中的IntBuffer -> http://docs.oracle.com/javase/6/docs/api/java/nio/IntBuffer.html
int[] intArray = new int[ 5000000 ];

IntBuffer intBuffer = IntBuffer.wrap( intArray );

...

通过调用inChannel.read(intBuffer)来填充缓冲区。

一旦缓冲区已满,您的intArray将包含500000个整数。

编辑

意识到通道仅支持ByteBuffer后。

// asume I already know that there are now 500 000 int to read:
int numInts = 500000;
// here I want the result into
int[] result = new int[numInts];

// 4 bytes per int, direct buffer
ByteBuffer buf = ByteBuffer.allocateDirect( numInts * 4 );

// BIG_ENDIAN byte order
buf.order( ByteOrder.BIG_ENDIAN );

// Fill in the buffer
while ( buf.hasRemaining( ) )
{
   // Per EJP's suggestion check EOF condition
   if( inChannel.read( buf ) == -1 )
   {
       // Hit EOF
       throw new EOFException( );
   }
}

buf.flip( );

// Create IntBuffer view
IntBuffer intBuffer = buf.asIntBuffer( );

// result will now contain all ints read from file
intBuffer.get( result );

我已经尝试过了,但我卡在了“int bytesRead = inChannel.read(intBuffer);”这里。这段代码无法编译,因为我不能将IntBuffer传递给inChannel.read(),它需要一个byteBuffer。 - AlexWien
读取循环不足够。如果遇到过早的EOF,它将永远运行。您应该在read()返回正数时循环。这会测试EOF和hasRemaining() - user207421

2
我进行了一项相当谨慎的实验,使用serialize / deserialize,DataInputStream与ObjectInputStream进行比较,两者都基于ByteArrayInputStream以避免IO效应。对于100万个整数,readObject大约需要20毫秒,readInt大约需要116毫秒。对于100万个整数数组,序列化开销为27字节。这是在2013年左右的MacBook Pro上进行的。
话虽如此,对象序列化有点邪恶,您必须使用Java程序将数据写出。

这很有趣,我没有考虑过使用writeObject的可能性。在写入之前,writeObject内部使用Bits.putInt()填充一个byte[]。这可能比简单地调用一百万次writeInt()更快。(java.nio在PC上比java.io更快,因为它使用DMA访问磁盘,而该嵌入式设备上不可用) - AlexWien

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