从java

时间:2017-02-20 20:54:11

标签: java memory-mapped-files randomaccessfile filechannel

我正在研究DNG / TIFF文件的读写器。由于有一些选项可以处理一般的文件(FileInputStreamFileChannelRandomAccessFile),我想知道哪种策略符合我的需求。

DNG / TIFF文件的组成为:

  • 一些(5-20​​)个小块(几十到几百字节)
  • 极少数(1-3)大的连续图像数据块(最高100 MiB)
  • 几个(可能是20-50个)非常小的块(4-16个字节)

整个文件大小范围从15 MiB(压缩的14位原始数据)到大约100 MiB(未压缩的浮点数据)。要处理的文件数为50-400。

有两种使用模式:

  1. 读取所有文件中的所有元数据(除图像数据外的所有内容)
  2. 从所有文件中读取所有图像数据
  3. 我目前正在使用FileChannel并执行map()来获取覆盖整个文件的MappedByteBuffer。如果我只是对阅读元数据感兴趣,这似乎很浪费。另一个问题是释放映射的内存:当我传递映射缓冲区的片段以进行解析等时,将不会收集基础MappedByteBuffer

    我现在决定使用多个FileChannel方法复制较小的read()块,并仅映射大的原始数据区域。缺点是读取单个值似乎非常复杂,因为没有readShort()之类的东西:

    short readShort(long offset) throws IOException, InterruptedException {
        return read(offset, Short.BYTES).getShort();
    }
    
    ByteBuffer read(long offset, long byteCount) throws IOException, InterruptedException {
        ByteBuffer buffer = ByteBuffer.allocate(Math.toIntExact(byteCount));
        buffer.order(GenericTiffFileReader.this.byteOrder);
        GenericTiffFileReader.this.readInto(buffer, offset);
        return buffer;
    }
    
    private void readInto(ByteBuffer buffer, long startOffset)
            throws IOException, InterruptedException {
    
        long offset = startOffset;
        while (buffer.hasRemaining()) {
            int bytesRead = this.channel.read(buffer, offset);
            switch (bytesRead) {
            case 0:
                Thread.sleep(10);
                break;
            case -1:
                throw new EOFException("unexpected end of file");
            default:
                offset += bytesRead;
            }
        }
        buffer.flip();
    }
    

    RandomAccessFile提供了有用的方法,如readShort()readFully(),但无法处理小端字节顺序。

    那么,是否有一种惯用的方法来处理单个字节和大块的分散读取?内存映射整个100 MiB文件只读取几百个字节是浪费还是慢?

1 个答案:

答案 0 :(得分:0)

好的,我终于做了一些粗略的基准测试:

  1. 刷新所有读取缓存echo 3 > /proc/sys/vm/drop_caches
  2. 重复8次:从每个文件中读取1000次8字节(从20 MiB到1 GiB的大约20个文件)。
  3. 文件的总和'大小超过了我安装的系统内存。

    方法1,FileChannel和临时ByteBuffer s:

    private static long method1(Path file, long dummyUsage) throws IOException, Error {
        try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
    
            for (int i = 0; i < 1000; i++) {
                ByteBuffer dst = ByteBuffer.allocate(8);
    
                if (channel.position(i * 10000).read(dst) != dst.capacity())
                    throw new Error("partial read");
                dst.flip();
                dummyUsage += dst.order(ByteOrder.LITTLE_ENDIAN).getInt();
                dummyUsage += dst.order(ByteOrder.BIG_ENDIAN).getInt();
            }
        }
        return dummyUsage;
    }
    

    结果:

    1. 3422 ms
    2. 56 ms
    3. 24 ms
    4. 24 ms
    5. 27 ms
    6. 25 ms
    7. 23 ms
    8. 23 ms
    

    方法2,MappedByteBuffer覆盖整个文件:

    private static long method2(Path file, long dummyUsage) throws IOException {
    
        final MappedByteBuffer buffer;
        try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
            buffer = channel.map(MapMode.READ_ONLY, 0L, Files.size(file));
        }
        for (int i = 0; i < 1000; i++) {
            dummyUsage += buffer.order(ByteOrder.LITTLE_ENDIAN).getInt(i * 10000);
            dummyUsage += buffer.order(ByteOrder.BIG_ENDIAN).getInt(i * 10000 + 4);
        }
        return dummyUsage;
    }
    

    结果:

    1. 749 ms
    2. 21 ms
    3. 17 ms
    4. 16 ms
    5. 18 ms
    6. 13 ms
    7. 15 ms
    8. 17 ms
    

    方法3,RandomAccessFile

    private static long method3(Path file, long dummyUsage) throws IOException {
    
        try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "r")) {
            for (int i = 0; i < 1000; i++) {
    
                raf.seek(i * 10000);
                dummyUsage += Integer.reverseBytes(raf.readInt());
                raf.seek(i * 10000 + 4);
                dummyUsage += raf.readInt();
            }
        }
        return dummyUsage;
    }
    

    结果:

    1. 3479 ms
    2. 104 ms
    3. 81 ms
    4. 84 ms
    5. 78 ms
    6. 81 ms
    7. 81 ms
    8. 81 ms
    

    结论:MappedByteBuffer - 方法占用更多的页面缓存内存(340 MB而不是140 MB),但在第一次和所有后续运行中表现更好,并且似乎具有最低的开销。作为奖励,这种方法提供了关于字节顺序,分散的小数据和巨大数据块的非常舒适的界面。 RandomAccessFile表现最差。

    回答我自己的问题:覆盖整个文件的MappedByteBuffer似乎是处理大文件随机访问的惯用且最快的方式,而不会浪费内存。