63 votes

Lire des fichiers volumineux en Java

J'ai besoin de l'avis de quelqu'un qui connaît très bien Java et les problèmes de mémoire. J'ai un gros fichier (environ 1,5 Go) et je dois le découper en plusieurs fichiers plus petits (100 petits fichiers par exemple).

Je sais généralement comment le faire (en utilisant une BufferedReader ), mais j'aimerais savoir si vous avez des conseils concernant la mémoire, ou des astuces pour le faire plus rapidement.

Mon fichier contient du texte, il n'est pas binaire et j'ai environ 20 caractères par ligne.

7 votes

Utilisez des API d'octets (par exemple FileInputStream, ByteChannel), plutôt que des API de caractères (BufferedReader, etc.). Sinon, vous encodez et décodez inutilement.

3 votes

Diviser un fichier texte en utilisant des octets serait une mauvaise idée.

35voto

BalusC Points 498232

Pour économiser de la mémoire, ne stockez pas/ne dupliquez pas inutilement les données en mémoire (c'est-à-dire ne les affectez pas à des variables en dehors de la boucle). Traitez simplement la sortie immédiatement dès que l'entrée se fait.

Cela n'a vraiment aucune importance que vous utilisiez BufferedReader ou pas. Cela ne coûtera pas beaucoup plus de mémoire comme certains semblent le suggérer implicitement. Au mieux, cela n'affectera que quelques % des performances. Il en va de même pour l'utilisation de NIO. Elle n'améliorera que l'évolutivité, pas l'utilisation de la mémoire. Cela ne deviendra intéressant que lorsque des centaines de threads fonctionneront sur le même fichier.

Il suffit de parcourir le fichier en boucle, d'écrire chaque ligne immédiatement dans l'autre fichier au fur et à mesure de la lecture, de compter les lignes et s'il atteint 100, de passer au fichier suivant, et ainsi de suite.

Exemple de coup d'envoi :

String encoding = "UTF-8";
int maxlines = 100;
BufferedReader reader = null;
BufferedWriter writer = null;

try {
    reader = new BufferedReader(new InputStreamReader(new FileInputStream("/bigfile.txt"), encoding));
    int count = 0;
    for (String line; (line = reader.readLine()) != null;) {
        if (count++ % maxlines == 0) {
            close(writer);
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/smallfile" + (count / maxlines) + ".txt"), encoding));
        }
        writer.write(line);
        writer.newLine();
    }
} finally {
    close(writer);
    close(reader);
}

0 votes

Oui, il suffit de le faire passer du FileInputStream au FilOutputStream en utilisant uniquement un tableau tampon d'octets de taille appropriée.

0 votes

Il ne fonctionne pas pour moi pour compter les lignes. Le problème est le suivant : j'ai un fichier et je dois le diviser en 200 (cela peut changer, cela viendra de la base de données) fichiers par exemple. Comment dois-je faire ? Le simple fait de compter les lignes ne fonctionne pas. Comment faire autrement ?

0 votes

Comptez alors le nombre d'octets écrits au lieu du nombre de lignes. Vous pouvez ainsi connaître à l'avance la taille du fichier en octets.

31voto

Michael Borgwardt Points 181658

Tout d'abord, si votre fichier contient des données binaires, alors l'utilisation de la fonction BufferedReader serait une grave erreur (car vous convertiriez les données en String, ce qui n'est pas nécessaire et pourrait facilement corrompre les données) ; vous devriez utiliser un fichier BufferedInputStream à la place. S'il s'agit de données de type texte et que vous devez les diviser le long de sauts de ligne, vous pouvez alors utiliser BufferedReader est OK (en supposant que le fichier contient des lignes d'une longueur raisonnable).

En ce qui concerne la mémoire, il ne devrait pas y avoir de problème si vous utilisez un tampon de taille décente (j'utiliserais au moins 1 Mo pour m'assurer que le disque dur effectue principalement des lectures et des écritures séquentielles).

Si la vitesse s'avère être un problème, vous pouvez jeter un coup d'œil à l'interface de l'entreprise. java.nio Ces paquets sont censés être plus rapides que java.io ,

0 votes

Oui, je vais utiliser BufferedReader car j'ai un fichier texte et je dois le lire ligne par ligne. Maintenant, j'ai un autre problème : je ne peux pas détecter la taille du nouveau fichier lors de son écriture. L'idée est de générer un nouveau fichier lorsque la taille du nouveau fichier est > xx MB.

1 votes

@CC : vous pourriez simplement continuer à additionner la longueur de chaîne des lignes que vous copiez. Mais cela dépend du codage des caractères et de la façon dont cela se traduit en taille de fichier (et ne fonctionne pas bien du tout avec les codages à longueur variable tels que UTF-8).

1 votes

Je suggère d'ajouter un FilterOutputStream personnalisé entre le FileOutputStream (en bas) et le OutputStreamWriter. Implémentez ce filtre pour garder la trace du nombre d'octets qui le traverse (apache commons io a peut-être déjà un tel utilitaire).

13voto

Ryan Emerle Points 8073

Vous pouvez envisager d'utiliser des fichiers mappés en mémoire, via Canal de fichiers s .

Généralement beaucoup plus rapide pour les gros fichiers. Il existe des compromis en matière de performances qui pourrait le rendre plus lent, donc YMMV.

Réponse connexe : Performances / utilité de Java NIO FileChannel par rapport à FileOutputstream

0 votes

Si vous ne faites que lire directement un fichier, cela ne vous apportera probablement pas grand-chose.

0 votes

Généralement no beaucoup plus rapidement. La dernière fois que je l'ai testé, j'ai eu 20% en lecture.

6voto

b.roth Points 4198

C'est un très bon article : http://java.sun.com/developer/technicalArticles/Programming/PerfTuning/

En résumé, pour obtenir de bonnes performances, vous devez :

  1. Évitez d'accéder au disque.
  2. Évitez d'accéder au système d'exploitation sous-jacent.
  3. Évitez les appels de méthode.
  4. Évitez de traiter les octets et les caractères individuellement.

Par exemple, pour réduire l'accès au disque, vous pouvez utiliser une grande mémoire tampon. L'article décrit différentes approches.

4voto

Mike Points 1449

Faut-il le faire en Java ? En d'autres termes, doit-il être indépendant de la plate-forme ? Si ce n'est pas le cas, je vous suggère d'utiliser l'option ' divisé dans *nix. Si vous le voulez vraiment, vous pouvez exécuter cette commande via votre programme Java. Bien que je n'aie pas testé, j'imagine qu'elle est plus rapide que n'importe quelle implémentation Java IO que vous pourriez trouver.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X