web-dev-qa-db-fra.com

Nombre de lignes dans un fichier dans Java

J'utilise d'énormes fichiers de données. Parfois, je n'ai besoin que de connaître le nombre de lignes dans ces fichiers. Généralement, je les ouvre et les lis ligne par ligne jusqu'à la fin du fichier.

Je me demandais s'il y avait un moyen plus intelligent de le faire

206
Mark

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 avec readLines (). Juste pour le plaisir, 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 Java, mais j’ai quand même essayé de comparer ce code à la solution LineNumberReader ci-dessous, car cela ne me dérangeait pas que personne ne l’ait fait. Il semble que ma solution est plus rapide, surtout pour les gros fichiers. Bien que cela semble prendre quelques essais jusqu'à ce que l'optimiseur fasse un travail décent. J'ai un peu joué avec le code et j'ai produit une nouvelle version 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) {
            System.out.println(readChars);
            for (int i=0; i<readChars; ++i) {
                if (c[i] == '\n') {
                    ++count;
                }
            }
            readChars = is.read(c);
        }

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

Le test repère pour un fichier texte de 1,3 Go, l’axe des y en secondes. J'ai effectué 100 analyses avec le même fichier et mesuré chaque analyse avec System.nanoTime(). Vous pouvez voir que countLinesOld a quelques valeurs aberrantes, et countLinesNew n'en a pas, et bien que ce ne soit qu'un peu plus rapide, la différence est statistiquement significative. LineNumberReader est clairement plus lent.

Benchmark Plot

230
martinus

J'ai implémenté une autre solution au problème, je l'ai trouvée plus efficace dans le comptage des lignes:

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
}
198
er.vikas

La réponse acceptée a une erreur par une erreur pour les fichiers multi-lignes 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 1 également. Voici une implémentation de la solution acceptée qui résout ce problème. Les contrôles endsWithoutNewLine gaspillent tout sauf la lecture finale, mais doivent être simples en termes de temps par rapport à la fonction globale.

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();
    }
}
28
DMulligan

Avec Java-8 , vous pouvez utiliser des flux:

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

La réponse avec la méthode count () ci-dessus m'a donné des erreurs de calcul de ligne si un fichier n'avait pas de nouvelle ligne à la fin du fichier - il n'a pas pu compter la dernière ligne du fichier.

Cette méthode fonctionne mieux pour moi:

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;
}
12
Dave Bergert

Je sais que c'est une vieille question, mais la solution acceptée ne correspond pas vraiment à ce que je devais faire. Donc, je l'ai affiné pour accepter divers terminateurs de ligne (plutôt que juste un saut de ligne) et utiliser un codage de caractères spécifié (plutôt que ISO-8859-n). Méthode tout en un (refactorisation selon le cas):

public static long getLinesCount(String fileName, String encodingName) throws IOException {
    long linesCount = 0;
    File file = new File(fileName);
    FileInputStream fileIn = new FileInputStream(file);
    try {
        Charset encoding = Charset.forName(encodingName);
        Reader fileReader = new InputStreamReader(fileIn, encoding);
        int bufferSize = 4096;
        Reader reader = new BufferedReader(fileReader, bufferSize);
        char[] buffer = new char[bufferSize];
        int prevChar = -1;
        int readCount = reader.read(buffer);
        while (readCount != -1) {
            for (int i = 0; i < readCount; i++) {
                int nextChar = buffer[i];
                switch (nextChar) {
                    case '\r': {
                        // The current line is terminated by a carriage return or by a carriage return immediately followed by a line feed.
                        linesCount++;
                        break;
                    }
                    case '\n': {
                        if (prevChar == '\r') {
                            // The current line is terminated by a carriage return immediately followed by a line feed.
                            // The line has already been counted.
                        } else {
                            // The current line is terminated by a line feed.
                            linesCount++;
                        }
                        break;
                    }
                }
                prevChar = nextChar;
            }
            readCount = reader.read(buffer);
        }
        if (prevCh != -1) {
            switch (prevCh) {
                case '\r':
                case '\n': {
                    // The last line is terminated by a line terminator.
                    // The last line has already been counted.
                    break;
                }
                default: {
                    // The last line is terminated by end-of-file.
                    linesCount++;
                }
            }
        }
    } finally {
        fileIn.close();
    }
    return linesCount;
}

Cette solution est comparable en termes de rapidité à la solution acceptée, soit environ 4% de moins dans mes tests (bien que les tests de synchronisation dans Java soient notoirement peu fiables).

8
Nathan Ryan

J'ai testé les méthodes ci-dessus pour compter les lignes et voici mes observations pour Différentes méthodes testées sur mon système

Taille du fichier: 1.6 Go Méthodes:

  1. à l'aide d'un scanner: environ 35 secondes
  2. tilisation de BufferedReader: environ 5 secondes
  3. tilisation de Java 8: environ 5 secondes.
  4. sing LineNumberReader: environ 5 secondes

De plus, l'approche Java8 semble assez pratique: Files.lines (Paths.get (cheminFichier), Charset.defaultCharset ()). Count () [Type de résultat: long]

5
Anshul
/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (Stream<String> lines = Files.lines(file.toPath())) {
        return lines.count();
    }
}

Testé sur JDK8_u31. Mais effectivement, les performances sont lentes comparées à cette méthode:

/**
 * Count file rows.
 *
 * @param file file
 * @return file row count
 * @throws IOException
 */
public static long getLineCount(File file) throws IOException {

    try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), 1024)) {

        byte[] c = new byte[1024];
        boolean empty = true,
                lastEmpty = false;
        long count = 0;
        int read;
        while ((read = is.read(c)) != -1) {
            for (int i = 0; i < read; i++) {
                if (c[i] == '\n') {
                    count++;
                    lastEmpty = true;
                } else if (lastEmpty) {
                    lastEmpty = false;
                }
            }
            empty = false;
        }

        if (!empty) {
            if (count == 0) {
                count = 1;
            } else if (!lastEmpty) {
                count++;
            }
        }

        return count;
    }
}

Testé et très rapide.

4
Ernestas Gruodis

Un moyen simple d'utiliser Scanner

static void lineCounter (String path) throws IOException {

        int lineCount = 0, commentsCount = 0;

        Scanner input = new Scanner(new File(path));
        while (input.hasNextLine()) {
            String data = input.nextLine();

            if (data.startsWith("//")) commentsCount++;

            lineCount++;
        }

        System.out.println("Line Count: " + lineCount + "\t Comments Count: " + commentsCount);
    }
3
Terry Bu

J'ai conclu que la méthode wc -l: s de comptage des nouvelles lignes convient, mais renvoie des résultats non intuitifs sur les fichiers dont la dernière ligne ne se termine pas par un retour à la ligne.

Et la solution @ er.vikas basée sur LineNumberReader, mais en ajoutant un au nombre de lignes, les résultats n'étaient pas intuitifs pour les fichiers dont la dernière ligne se terminait par une nouvelle ligne.

J'ai donc fait un algo qui gère comme suit:

@Test
public void empty() throws IOException {
    assertEquals(0, count(""));
}

@Test
public void singleNewline() throws IOException {
    assertEquals(1, count("\n"));
}

@Test
public void dataWithoutNewline() throws IOException {
    assertEquals(1, count("one"));
}

@Test
public void oneCompleteLine() throws IOException {
    assertEquals(1, count("one\n"));
}

@Test
public void twoCompleteLines() throws IOException {
    assertEquals(2, count("one\ntwo\n"));
}

@Test
public void twoLinesWithoutNewlineAtEnd() throws IOException {
    assertEquals(2, count("one\ntwo"));
}

@Test
public void aFewLines() throws IOException {
    assertEquals(5, count("one\ntwo\nthree\nfour\nfive\n"));
}

Et ça ressemble à ça:

static long countLines(InputStream is) throws IOException {
    try(LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is))) {
        char[] buf = new char[8192];
        int n, previousN = -1;
        //Read will return at least one byte, no need to buffer more
        while((n = lnr.read(buf)) != -1) {
            previousN = n;
        }
        int ln = lnr.getLineNumber();
        if (previousN == -1) {
            //No data read at all, i.e file was empty
            return 0;
        } else {
            char lastChar = buf[previousN - 1];
            if (lastChar == '\n' || lastChar == '\r') {
                //Ending with newline, deduct one
                return ln;
            }
        }
        //normal case, return line number + 1
        return ln + 1;
    }
}

Si vous voulez des résultats intuitifs, vous pouvez utiliser ceci. Si vous voulez juste la compatibilité wc -l, utilisez simplement la solution @ er.vikas, mais n'en ajoutez pas un au résultat et réessayez le saut:

try(LineNumberReader lnr = new LineNumberReader(new FileReader(new File("File1")))) {
    while(lnr.skip(Long.MAX_VALUE) > 0){};
    return lnr.getLineNumber();
}
3
Alexander Torstling

Que diriez-vous d'utiliser la classe Process à partir du code Java? Et puis en lisant le résultat de la commande.

Process p = Runtime.getRuntime().exec("wc -l " + yourfilename);
p.waitFor();

BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
int lineCount = 0;
while ((line = b.readLine()) != null) {
    System.out.println(line);
    lineCount = Integer.parseInt(line);
}

Besoin d'essayer cependant. Afficherons les résultats.

2
Sunil Shevante

Cette solution amusante fonctionne vraiment bien en fait!

public static int countLines(File input) throws IOException {
    try (InputStream is = new FileInputStream(input)) {
        int count = 1;
        for (int aChar = 0; aChar != -1;aChar = is.read())
            count += aChar == '\n' ? 1 : 0;
        return count;
    }
}
1
Ilya Gazman

Si vous n'avez pas de structure d'index, vous ne pourrez pas contourner la lecture du fichier complet. Mais vous pouvez l’optimiser en évitant de le lire ligne par ligne et d’utiliser une expression régulière correspondant à tous les terminateurs de ligne.

1
David Schmitt

Sur les systèmes Unix, utilisez la commande wc sur la ligne de commande.

0
Peter Hilton

Le seul moyen de savoir combien de lignes il y a dans le fichier est de les compter. Vous pouvez bien sûr créer une métrique à partir de vos données en vous donnant une longueur moyenne d'une ligne, puis obtenir la taille du fichier et la diviser avec avg. longueur mais ça ne sera pas précis.

0
Esko

Scanner avec regex:

public int getLineCount() {
    Scanner fileScanner = null;
    int lineCount = 0;
    Pattern lineEndPattern = Pattern.compile("(?m)$");  
    try {
        fileScanner = new Scanner(new File(filename)).useDelimiter(lineEndPattern);
        while (fileScanner.hasNext()) {
            fileScanner.next();
            ++lineCount;
        }   
    }catch(FileNotFoundException e) {
        e.printStackTrace();
        return lineCount;
    }
    fileScanner.close();
    return lineCount;
}

Je n'ai pas chronométré.

0
user176692

Code optimisé pour les fichiers multilignes ne comportant pas de caractère de nouvelle ligne ('\ n') à EOF.

/**
 * 
 * @param filename
 * @return
 * @throws IOException
 */
public static int countLines(String filename) throws IOException {
    int count = 0;
    boolean empty = true;
    FileInputStream fis = null;
    InputStream is = null;
    try {
        fis = new FileInputStream(filename);
        is = new BufferedInputStream(fis);
        byte[] c = new byte[1024];
        int readChars = 0;
        boolean isLine = false;
        while ((readChars = is.read(c)) != -1) {
            empty = false;
            for (int i = 0; i < readChars; ++i) {
                if ( c[i] == '\n' ) {
                    isLine = false;
                    ++count;
                }else if(!isLine && c[i] != '\n' && c[i] != '\r'){   //Case to handle line count where no New Line character present at EOF
                    isLine = true;
                }
            }
        }
        if(isLine){
            ++count;
        }
    }catch(IOException e){
        e.printStackTrace();
    }finally {
        if(is != null){
            is.close();    
        }
        if(fis != null){
            fis.close();    
        }
    }
    LOG.info("count: "+count);
    return (count == 0 && !empty) ? 1 : count;
}
0
Pramod Yadav