2017-02-20 4 views
4

Je travaille sur un lecteur/écrivain pour les fichiers DNG/TIFF. Comme il existe plusieurs options pour travailler avec des fichiers en général (FileInputStream, FileChannel, RandomAccessFile), je me demande quelle stratégie correspondrait à mes besoins.Lire des données dispersées à partir de plusieurs fichiers dans Java

Un fichier DNG/TIFF est une composition de:

  • certains (petits blocs) 5-20 (plusieurs dizaines à centaines d'octets)
  • très peu (1-3) grands blocs continus de l'image données (jusqu'à 100 MiB)
  • plusieurs (peut-être 20 à 50) de très petits blocs (4-16 octets)

La taille du fichier global varie de 15 MiB (données brutes 14 bits compressé) jusqu'à environ 100 MiB (données flottantes non compressées). Le nombre de fichiers à traiter est compris entre 50 et 400.

Il existe deux modèles d'utilisation:

  1. lire toutes les méta-données de tous les fichiers (tout sauf les données d'image)
  2. Lire toutes les données d'image de tous les fichiers

Je suis actuellement en utilisant un FileChannel et en effectuant un map() pour obtenir un MappedByteBuffer couvrant l'ensemble du fichier. Cela semble tout à fait inutile si je suis simplement intéressé par la lecture des méta-données. Un autre problème est la libération de la mémoire mappée: lorsque je passe des tranches du tampon mappé pour l'analyse, le MappedByteBuffer sous-jacent ne sera pas collecté.

J'ai maintenant décidé de copier de plus petits blocs de FileChannel en utilisant les plusieurs méthodes read() et de mapper uniquement les grandes régions de données brutes. L'inconvénient est que la lecture d'une valeur unique semble extrêmement complexe, car il n'y a pas readShort() et articles similaires:

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 fournit des méthodes utiles comme readShort() ou readFully(), mais ne peut pas gérer peu l'ordre des octets endian. Donc, y a-t-il une façon idiomatique de gérer les lectures dispersées d'octets simples et de blocs énormes? Est-ce que le mappage de la mémoire contient un fichier entier de 100 Mio pour juste lire quelques centaines d'octets inutiles ou lents?

+0

Utilisez un seul 'ByteBuffer' pour toutes les lectures. Les créer est plutôt cher. – EJP

+0

La pré-allocation du 'ByteBuffer' ne sera pas possible dans l'application finale car la taille sera dynamique et interdira l'utilisation simultanée. –

Répondre

0

Ok, j'ai finalement fait quelques points de repère rugueux:

  1. Rincer Lus caches echo 3 > /proc/sys/vm/drop_caches
  2. Répétez 8 fois: Lire 1000 fois 8 octets de chaque fichier (environ 20 fichiers de 20 MiB jusqu'à 1 Gio).

La somme des tailles de fichiers a dépassé ma mémoire système installée.

Méthode 1, FileChannel et ByteBuffer temporaire 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; 
} 

Résultats:

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

Méthode 2, MappedByteBuffer couvrant la totalité du fichier:

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; 
} 

Résultats:

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

Méthode 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; 
} 

Résultats:

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

Conclusion: Le MappedByteBuffer -method occupe plus de mémoire cache de page (340 MB au lieu de 140 Mo), mais nettement plus performant sur la première et toutes les exécutions suivantes et semble avoir le plus bas frais généraux. Et en prime, cette méthode fournit une interface vraiment confortable concernant l'ordre des octets, les petites données dispersées et les énormes blocs de données. RandomAccessFile effectue les pires.

Pour répondre à ma propre question: Un MappedByteBuffer couvrant l'ensemble du fichier semble être le moyen idiomatique et le plus rapide pour gérer l'accès aléatoire aux gros fichiers sans gaspillage de mémoire.