2017-02-20 115 views
4

我正在研究DNG/TIFF文件的讀寫器。由於有幾個選項可用於處理文件(FileInputStream,FileChannel,RandomAccessFile),我想知道哪種策略適合我的需求。從java中的多個文件讀取零散數據

甲DNG/TIFF文件是一個組合物:

  • 一些(5-20​​)小塊(幾十到幾百個字節)
  • 很少(1-3)大的連續圖像的塊數據(高達100 MIB)
  • 幾個(或許20-50)非常小的塊(4-16字節)

的整個文件的大小從15 MIB(壓縮14位原始數據)的範圍內至多約100 MiB(未壓縮的浮點數據)。要處理的文件數是50-400。

有兩種使用模式:

  1. 閱讀所有文件中的所有元數據(以外的所有圖像數據)
  2. 從所有文件中讀取所有的圖像數據

我目前使用FileChannel並執行map()以獲得覆蓋整個文件的MappedByteBuffer。如果我只是對閱讀元數據感興趣,這看起來很浪費。另一個問題是釋放映射的內存:當我傳遞映射緩衝區的片段進行解析時,將不會收集底層的MappedByteBuffer

我現在決定使用幾個read()-方法複製較小的塊FileChannel,並且只映射大的原始數據區域。不足之處是讀取一個價值似乎非常複雜,因爲沒有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文件只讀幾百字節浪費或緩慢?

+0

對所有讀取使用一個「ByteBuffer」。創建它們相當昂貴。 – EJP

+0

預分配'ByteBuffer'在最終的應用程序中是不可能的,因爲大小將是動態的,並且會禁止併發使用。 –

回答

0

好吧,我終於做了一些粗略的基準:

  1. 刷新所有讀取緩存echo 3 > /proc/sys/vm/drop_caches
  2. 重複8次:從每個文件閱讀1000首8個字節(20 MIB約20個文件長達1吉布)。

文件大小的總和超過了我安裝的系統內存。

方法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 -method佔用更多頁面的高速緩衝存儲器(而不是340 MB 140 MB),但顯著更好執行上第一次和所有後續運行,似乎有最低的開銷。作爲獎勵,該方法爲字節順序,分散的小數據和巨大的數據塊提供了一個非常舒適的接口。 RandomAccessFile表現最差。

回答我自己的問題:覆蓋整個文件的MappedByteBuffer似乎是處理隨機訪問大文件而不浪費內存的慣用和最快的方式。