web-dev-qa-db-fra.com

Le moyen le plus rapide de lire incrémentalement un gros fichier

Lorsqu'on lui donne un tampon de MAX_BUFFER_SIZE et un fichier qui le dépasse de loin, comment peut-on:

  1. Lire le fichier en blocs de MAX_BUFFER_SIZE?
  2. Faites-le aussi vite que possible

J'ai essayé d'utiliser NIO

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

    ByteBuffer buffer = ByteBuffer.allocate(CAPARICY);

    int bytesRead = inChannel.read(buffer);

    buffer.flip();

        while (buffer.hasRemaining()) {
            buffer.get();
        }

        buffer.clear();
        bytesRead = inChannel.read(buffer);

    aFile.close();

Et IO régulier

    InputStream in = new FileInputStream(fileName);

    long length = fileName.length();

    if (length > Integer.MAX_VALUE) {
        throw new IOException("File is too large!");
    }

    byte[] bytes = new byte[(int) length];

    int offset = 0;

    int numRead = 0;

    while (offset < bytes.length
            && (numRead = in.read(bytes, offset, bytes.length - offset)) >= 0) {
        offset += numRead;
    }

    if (offset < bytes.length) {
        throw new IOException("Could not completely read file " + fileName);
    }

    in.close();

Il s'avère que regular IO est environ 100 fois plus rapide à faire la même chose que NIO. Suis-je en train de manquer quelque chose? Est-ce prévu? Existe-t-il un moyen plus rapide de lire le fichier dans des blocs de tampon?

En fin de compte, je travaille avec un gros fichier pour lequel je n'ai pas de mémoire pour tout lire en même temps. Au lieu de cela, j'aimerais le lire de manière incrémentielle dans des blocs qui seraient ensuite utilisés pour le traitement.

19
James Raitsev

En supposant que vous ayez besoin de lire le fichier entier en mémoire à la fois (comme vous le faites actuellement), ni la lecture de petits morceaux ni NIO ne vous aideront ici.

En fait, vous feriez probablement mieux de lire des morceaux plus gros - ce que votre code IO normal fait automatiquement pour vous.

Votre code NIO est actuellement plus lent, car vous ne lisez qu'un octet à la fois (en utilisant buffer.get();).

Si vous souhaitez traiter par blocs - par exemple, transférer entre les flux - voici une manière standard de le faire sans NIO:

InputStream is = ...;
OutputStream os = ...;

byte buffer[] = new byte[1024];
int read;
while((read = is.read(buffer)) != -1){
    os.write(buffer, 0, read);
}

Cela utilise une taille de tampon de seulement 1 Ko, mais peut transférer une quantité illimitée de données.

(Si vous étendez votre réponse avec des détails sur ce que vous cherchez réellement à faire au niveau fonctionnel, je pourrais encore améliorer cela pour obtenir une meilleure réponse.)

20
ziesemer

Si vous voulez rendre votre premier exemple plus rapide

FileChannel inChannel = new FileInputStream(fileName).getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(CAPACITY);

while(inChannel.read(buffer) > 0)
    buffer.clear(); // do something with the data and clear/compact it.

inChannel.close();

Si vous voulez que ce soit encore plus rapide.

FileChannel inChannel = new RandomAccessFile(fileName, "r").getChannel();
MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
// access the buffer as you wish.
inChannel.close();

Cela peut prendre 10 à 20 micro-secondes pour des fichiers d'une taille maximale de 2 Go.

23
Peter Lawrey