228 votes

Nombre de lignes dans un fichier en Java

J'utilise d'énormes fichiers de données, parfois je n'ai besoin que du nombre de lignes dans ces fichiers, généralement je les ouvre et les lis ligne par ligne jusqu'à ce que j'atteigne la fin du fichier.

Je me demandais s'il existait une manière plus intelligente de procéder.

249voto

martinus Points 6895

C'est la version la plus rapide que j'ai trouvée jusqu'à présent, environ 6 fois plus rapide que readLines. Sur un fichier journal de 150 Mo, cela prend 0,35 seconde, contre 2,40 secondes en utilisant readLines(). Pour l'anecdote, la commande wc -l de linux prend 0,15 seconde.

public static int countLinesOld(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean empty = true;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
        }
        return (count == 0 && !empty) ? 1 : count;
    } finally {
        is.close();
    }
}

EDIT, 9 ans et demi plus tard : Je n'ai pratiquement aucune expérience en Java, mais j'ai tout de même essayé de comparer ce code à la norme LineNumberReader solution ci-dessous car cela me dérangeait que personne ne l'ait fait. Il semble que ma solution soit plus rapide, surtout pour les gros fichiers. Bien qu'il faille quelques exécutions avant que l'optimiseur ne fasse un travail décent. J'ai joué un peu avec le code, et j'ai produit une nouvelle version qui est toujours la plus rapide :

public static int countLinesNew(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];

        int readChars = is.read(c);
        if (readChars == -1) {
            // bail out if nothing to read
            return 0;
        }

        // make it easy for the optimizer to tune this loop
        int count = 0;
        while (readChars == 1024) {
            for (int i=0; i<1024;) {
                if (c[i++] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        // count remaining characters
        while (readChars != -1) {
            for (int i=0; i<readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

        return count == 0 ? 1 : count;
    } finally {
        is.close();
    }
}

Résultats du benchmark pour un fichier texte de 1.3GB, axe y en secondes. J'ai effectué 100 exécutions avec le même fichier, et j'ai mesuré chaque exécution avec System.nanoTime() . Vous pouvez constater que countLinesOld comporte quelques valeurs aberrantes, et countLinesNew n'en a pas et, bien qu'il ne soit que légèrement plus rapide, la différence est statistiquement significative. LineNumberReader est nettement plus lent.

Benchmark Plot

0 votes

Tu avais raison David, je pensais que la JVM serait suffisante pour cela... J'ai mis à jour le code, celui-ci est plus rapide.

5 votes

BufferedInputStream devrait faire la mise en mémoire tampon pour vous, donc je ne vois pas comment l'utilisation d'un tableau intermédiaire d'octets[] rendrait les choses plus rapides. Il est peu probable que vous fassiez beaucoup mieux que d'utiliser readLine() de manière répétée de toute façon (puisque cela sera optimisé par l'API).

0 votes

J'ai fait des tests avec et sans le flux d'entrée tamponné, et il est plus rapide lorsqu'il est utilisé.

201voto

er.vikas Points 879

J'ai mis en œuvre une autre solution au problème, je l'ai trouvée plus efficace pour compter les rangées :

try
(
   FileReader       input = new FileReader("input.txt");
   LineNumberReader count = new LineNumberReader(input);
)
{
   while (count.skip(Long.MAX_VALUE) > 0)
   {
      // Loop just in case the file is > Long.MAX_VALUE or skip() decides to not read the entire file
   }

   result = count.getLineNumber() + 1;                                    // +1 because line index starts at 0
}

0 votes

LineNumberReader 's lineNumber est un entier... Les fichiers plus longs que Integer.MAX_VALUE ne seront-ils pas simplement enveloppés ? Pourquoi s'embêter à sauter une longueur ici ?

1 votes

L'ajout d'une unité au décompte est en fait incorrect. wc -l compte le nombre de caractères de retour à la ligne dans le fichier. Cela fonctionne puisque chaque ligne se termine par un saut de ligne, y compris la dernière ligne d'un fichier. Chaque ligne comporte un caractère de retour à la ligne, y compris les lignes vides, ce qui signifie que le nombre de caractères de retour à la ligne = = nombre de lignes dans un fichier. Maintenant, la fonction lineNumber variable en FileNumberReader représente également le nombre de caractères de retour à la ligne observés. Il commence à zéro, avant qu'une nouvelle ligne n'ait été trouvée, et augmente à chaque nouveau caractère de ligne vu. N'ajoutez donc pas un au numéro de ligne, s'il vous plaît.

1 votes

@PB_MLT : Bien que vous ayez raison de dire qu'un fichier avec une seule ligne sans nouvelle ligne serait signalé comme 0 ligne, c'est ainsi que wc -l signale également ce type de fichier. Voir aussi stackoverflow.com/questions/729692/

30voto

AFinkelstein Points 3291

La réponse acceptée comporte une erreur de décalage pour les fichiers multilignes qui ne se terminent pas par une nouvelle ligne. Un fichier d'une ligne se terminant sans nouvelle ligne renverrait 1, mais un fichier de deux lignes se terminant sans nouvelle ligne renverrait également 1. Voici une implémentation de la solution acceptée qui corrige ce problème. Les vérifications de endsWithoutNewLine sont inutiles pour tout ce qui n'est pas la lecture finale, mais devraient être triviales en termes de temps par rapport à l'ensemble de la fonction.

public int count(String filename) throws IOException {
    InputStream is = new BufferedInputStream(new FileInputStream(filename));
    try {
        byte[] c = new byte[1024];
        int count = 0;
        int readChars = 0;
        boolean endsWithoutNewLine = false;
        while ((readChars = is.read(c)) != -1) {
            for (int i = 0; i < readChars; ++i) {
                if (c[i] == '\n')
                    ++count;
            }
            endsWithoutNewLine = (c[readChars - 1] != '\n');
        }
        if(endsWithoutNewLine) {
            ++count;
        } 
        return count;
    } finally {
        is.close();
    }
}

6 votes

Bien vu. Je ne sais pas pourquoi vous n'avez pas simplement édité la réponse acceptée et fait une note dans un commentaire. La plupart des gens ne liront pas jusqu'ici.

0 votes

@Ryan , il n'était pas normal d'éditer une réponse acceptée il y a 4 ans avec 90+ upvotes.

0 votes

@AFinkelstein, je pense que c'est ce qui rend ce site si formidable, que vous peut éditer la réponse la plus votée.

26voto

msayag Points 561

Con java-8 vous pouvez utiliser les flux :

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}

3 votes

Le code comporte des erreurs. Simple, mais très lent... Essayez de lire ma réponse ci-dessous (ci-dessus).

13voto

Dave Bergert Points 126

La réponse avec la méthode count() ci-dessus me donnait des erreurs de comptage de lignes si un fichier n'avait pas de nouvelle ligne à la fin du fichier - il ne comptait pas la dernière ligne du fichier.

Cette méthode me convient mieux :

public int countLines(String filename) throws IOException {
    LineNumberReader reader  = new LineNumberReader(new FileReader(filename));
int cnt = 0;
String lineRead = "";
while ((lineRead = reader.readLine()) != null) {}

cnt = reader.getLineNumber(); 
reader.close();
return cnt;
}

0 votes

Dans ce cas, il n'est pas nécessaire d'utiliser LineNumberReader, il suffit d'utiliser BufferedReader, ce qui permet d'utiliser le type de données long pour cnt .

0 votes

[INFO] Échec PMD:xx:19 Règle:EmptyWhileStmt Priorité:3 Évitez les instructions while vides.

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