Número de líneas en un archivo en Java

Resuelto Mark asked hace 15 años • 20 respuestas

Utilizo archivos de datos enormes, a veces solo necesito saber el número de líneas de estos archivos, normalmente los abro y los leo línea por línea hasta llegar al final del archivo.

Me preguntaba si hay una manera más inteligente de hacer eso.

Mark avatar Jan 17 '09 15:01 Mark
Aceptado

Esta es la versión más rápida que he encontrado hasta ahora, aproximadamente 6 veces más rápida que readLines. En un archivo de registro de 150 MB, esto demora 0,35 segundos, frente a 2,40 segundos cuando se utiliza readLines(). Solo por diversión, el comando wc -l de Linux tarda 0,15 segundos.

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

EDITAR, 9 años y medio después: prácticamente no tengo experiencia en Java, pero de todos modos he intentado comparar este código con la LineNumberReadersolución a continuación, ya que me molestaba que nadie lo hiciera. Parece que, especialmente para archivos grandes, mi solución es más rápida. Aunque parece que se necesitan algunas ejecuciones hasta que el optimizador haga un trabajo decente. Jugué un poco con el código y produje una nueva versión que es consistentemente más rápida:

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

Resultados de referencia para un archivo de texto de 1,3 GB, eje y en segundos. Realicé 100 ejecuciones con el mismo archivo y medí cada ejecución con System.nanoTime(). Puede ver que countLinesOldtiene algunos valores atípicos y countLinesNewninguno y, aunque es solo un poco más rápido, la diferencia es estadísticamente significativa. LineNumberReaderes claramente más lento.

Gráfico de referencia

martinus avatar Jan 17 '2009 09:01 martinus

Implementé otra solución al problema, la encontré más eficiente al contar filas:

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
}
er.vikas avatar Mar 17 '2011 16:03 er.vikas

La respuesta aceptada tiene un error de uno para archivos de varias líneas que no terminan en una nueva línea. Un archivo de una línea que termine sin una nueva línea devolverá 1, pero un archivo de dos líneas que termine sin una nueva línea también devolverá 1. Aquí hay una implementación de la solución aceptada que soluciona este problema. Las comprobaciones de EndsWithoutNewLine son un desperdicio para todo excepto para la lectura final, pero deberían ser triviales en términos de tiempo en comparación con la función general.

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();
    }
}
DMulligan avatar Jan 19 '2013 06:01 DMulligan