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

213

Utilizo grandes archivos de datos, a veces solo necesito saber la cantidad de líneas en estos archivos, generalmente los abro y los leo línea por línea hasta llegar al final del archivo

Me preguntaba si hay una forma más inteligente de hacerlo

marca
fuente

Respuestas:

237

Esta es la versión más rápida que he encontrado hasta ahora, aproximadamente 6 veces más rápido que readLines. En un archivo de registro de 150 MB, esto lleva 0,35 segundos, frente a 2,40 segundos cuando se usa 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 tratado de comparar este código con la LineNumberReadersolución a continuación, ya que me molestó que nadie lo hiciera. Parece que, especialmente para archivos grandes, mi solución es más rápida. Aunque parece tomar algunas carreras hasta que el optimizador hace un trabajo decente. He jugado un poco con el código y he producido 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) {
            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();
    }
}

Resultados de referencia para un archivo de texto de 1.3GB, eje y en segundos. He realizado 100 ejecuciones con el mismo archivo, y he medido 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.

Parcela de referencia

martinus
fuente
55
BufferedInputStream debería estar haciendo el almacenamiento en búfer por usted, por lo que no veo cómo el uso de una matriz de bytes intermedia [] lo hará más rápido. Es poco probable que lo haga mucho mejor que usar readLine () repetidamente de todos modos (ya que la API lo optimizará).
wds
54
Vas a cerrar ese InputStream cuando hayas terminado, ¿no?
bendin
55
Si el almacenamiento en búfer ayudó, lo haría porque BufferedInputStream almacena 8K de forma predeterminada. Aumente su byte [] a este tamaño o más grande y puede soltar el BufferedInputStream. Por ejemplo, pruebe 1024 * 1024 bytes.
Peter Lawrey
8
Dos cosas: (1) La definición de un terminador de línea en la fuente Java es un retorno de carro, un avance de línea o un retorno de carro seguido de un avance de línea. Su solución no funcionará para CR utilizado como un terminador de línea. De acuerdo, el único sistema operativo en el que puedo pensar que usa CR como el terminador de línea predeterminado es Mac OS antes que Mac OS X. (2) Su solución asume una codificación de caracteres como US-ASCII o UTF-8. El recuento de líneas puede ser inexacto para codificaciones como UTF-16.
Nathan Ryan
2
Código impresionante ... para un archivo de texto de 400 MB, tardó solo un segundo. Muchas gracias @martinus
user3181500
199

He implementado otra solución al problema, lo 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
fuente
LineNumberReaderEl lineNumbercampo de 'es un número entero ... ¿No se ajustará solo a los archivos más largos que Integer.MAX_VALUE? ¿Por qué molestarse en pasar tanto tiempo aquí?
epb
1
Agregar uno al conteo es realmente incorrecto. wc -lcuenta el número de caracteres de nueva línea en el archivo. Esto funciona ya que cada línea se termina con una nueva línea, incluida la línea final en un archivo. Cada línea tiene un carácter de nueva línea, incluidas las líneas vacías, de ahí que el número de caracteres de nueva línea == número de líneas en un archivo. Ahora, la lineNumbervariable en FileNumberReadertambién representa el número de caracteres de nueva línea vistos. Comienza en cero, antes de que se encuentre una nueva línea, y aumenta con cada carácter de nueva línea visto. Así que no agregue uno al número de línea por favor.
Alexander Torstling
1
@PB_MLT: Aunque tiene razón en que un archivo con una sola línea sin nueva línea se informará como 0 líneas, así es como wc -ltambién se informa este tipo de archivo. Ver también stackoverflow.com/questions/729692/…
Alexander Torstling
@PB_MLT: Obtiene el problema opuesto si el archivo consiste únicamente en una nueva línea. Su algo sugerido devolvería 0 y wc -ldevolvería 1. Llegué a la conclusión de que todos los métodos tienen fallas, e implementé uno basado en cómo me gustaría que se comportara, vea mi otra respuesta aquí.
Alexander Torstling
3
He votado en contra de esta respuesta, porque parece que ninguno de ustedes la ha comparado
Amstegraf
30

La respuesta aceptada tiene un error de uno por uno para los archivos de varias líneas que no terminan en nueva línea. Un archivo de una línea que termina sin una nueva línea devolvería 1, pero un archivo de dos líneas que termina sin una nueva línea también devolvería 1. Aquí hay una implementación de la solución aceptada que soluciona esto. Las comprobaciones finalesWithoutNewLine son un desperdicio para todo menos la lectura final, pero deben ser triviales en cuanto al 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
fuente
66
Buena atrapada. Sin embargo, no estoy seguro de por qué no solo editó la respuesta aceptada y anotó en un comentario. La mayoría de la gente no leerá hasta aquí.
Ryan
@ Ryan, simplemente no me pareció correcto editar una respuesta aceptada de 4 años con más de 90 votos a favor.
DMulligan
@AFinkelstein, siento que es lo que hace este sitio tan grande, que puede editar el más votadas respuesta.
Sebastian
3
Esta solución no maneja el retorno de carro (\ r) y el retorno de carro seguido de un salto de línea (\ r \ n)
Simon Brandhof - SonarSource
@ Simon Brandhof, estoy confundido sobre por qué un retorno de carro se contabilizaría como otra línea. Un "\ n" es un avance de línea de retorno de carro, por lo que quien escribe "\ r \ n" no está entendiendo algo ... Además, está buscando char por char, así que estoy bastante seguro de si alguien usaría "\ r \ n "aún capturaría el" \ n "y contaría la línea. De cualquier manera, creo que hizo bien el punto. Sin embargo, hay muchos escenarios en los que esta no es una forma suficiente de obtener un recuento de líneas.
nckbrz
22

Con , puedes usar transmisiones:

try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
  long numOfLines = lines.count();
  ...
}
msayag
fuente
1
El código tiene errores. Simple, pero muy lento ... Intenta ver mi respuesta a continuación (arriba).
Ernestas Gruodis
12

La respuesta con el método count () anterior me dio un recuento incorrecto de líneas si un archivo no tenía una nueva línea al final del archivo; no se pudo contar la última línea del archivo.

Este método funciona mejor para mí:

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;
}
Dave Bergert
fuente
En este caso, no es necesario usar LineNumberReader, simplemente use BufferedReader, en ese caso tendrá flexibilidad para usar tipos de datos largos cnt.
Syed Aqeel Ashiq
[INFO] PMD Failure: xx: 19 Rule: EmptyWhileStmt Prioridad: 3 Evite las declaraciones while vacías.
Chhorn Elit
8

Sé que esta es una vieja pregunta, pero la solución aceptada no coincidía con lo que necesitaba hacer. Entonces, lo refiné para aceptar varios terminadores de línea (en lugar de solo un avance de línea) y para usar una codificación de caracteres específica (en lugar de ISO-8859- n ). Método todo en uno (refactorizar según corresponda):

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

Esta solución es comparable en velocidad a la solución aceptada, aproximadamente un 4% más lenta en mis pruebas (aunque las pruebas de temporización en Java son notoriamente poco confiables).

Nathan Ryan
fuente
8

Probé los métodos anteriores para contar líneas y aquí están mis observaciones para diferentes métodos según lo probado en mi sistema

Tamaño de archivo: 1.6 Gb Métodos:

  1. Usando el escáner : 35 s aprox.
  2. Usando BufferedReader : 5s aprox.
  3. Usando Java 8 : 5s aprox.
  4. Usando LineNumberReader : 5s aprox.

Además, el enfoque Java8 parece bastante útil:

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]
Anshul
fuente
5
/**
 * 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();
    }
}

Probado en JDK8_u31. Pero, de hecho, el rendimiento es lento en comparación con este método:

/**
 * 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;
    }
}

Probado y muy rápido.

Ernestas Gruodis
fuente
Esto no es correcto Hizo algunos experimentos con su código y el método siempre es más lento. Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1Y el número de líneas es aún demasiado mal
AW-pensar
Probé en una máquina de 32 bits. Tal vez en 64 bits serían resultados diferentes ... Y fue la diferencia 10 veces o más, según recuerdo. ¿Podría publicar el texto para contar la línea en alguna parte? Puede usar Notepad2 para ver saltos de línea para mayor comodidad.
Ernestas Gruodis
Esa podría ser la diferencia.
aw-think
Si le importa el rendimiento, de BufferedInputStreamtodos modos no debe usar a cuando vaya a leer en su propio búfer. Además, incluso si su método puede tener una ligera ventaja de rendimiento, pierde flexibilidad, ya que ya no admite \rterminadores de línea única (MacOS antiguo) y no admite todas las codificaciones.
Holger
4

Una forma sencilla de usar el escáner

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);
    }
Terry Bu
fuente
3

Llegué a la conclusión de que wc -l: el método de contar nuevas líneas está bien, pero devuelve resultados no intuitivos en archivos donde la última línea no termina con una nueva línea.

Y la solución @ er.vikas basada en LineNumberReader pero agregando uno al recuento de líneas devolvió resultados no intuitivos en archivos donde la última línea termina con nueva línea.

Por lo tanto, hice un algo que se maneja de la siguiente manera:

@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"));
}

Y se ve así:

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 desea resultados intuitivos, puede usar esto. Si solo desea wc -lcompatibilidad, simplemente use la solución @ er.vikas, pero no agregue una al resultado y vuelva a intentar omitirla:

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

¿Qué tal usar la clase Process desde el código Java? Y luego leyendo la salida del comando.

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

Aunque necesito probarlo. Publicará los resultados.

Sunil Shevante
fuente
1

Si no tiene ninguna estructura de índice, no obtendrá la lectura del archivo completo. Pero puede optimizarlo evitando leerlo línea por línea y usar una expresión regular para que coincida con todos los terminadores de línea.

David Schmitt
fuente
Suena como una buena idea. ¿Alguien lo intentó y tiene una expresión regular para ello?
willcodejavaforfood
1
Dudo que sea una buena idea: necesitará leer todo el archivo a la vez (martinus lo evita) y las expresiones regulares son excesivas (y más lentas) para tal uso (búsqueda simple de caracteres fijos).
PhiLho
@will: ¿qué pasa con / \ n /? @PhiLo: Regex Executors son máquinas de rendimiento altamente optimizadas. Excepto la advertencia de leer todo en memoria, no creo que una implementación manual pueda ser más rápida.
David Schmitt
1

¡Esta divertida solución funciona realmente bien!

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;
    }
}
Ilya Gazman
fuente
0

En sistemas basados ​​en Unix, use el wccomando en la línea de comandos.

Peter Hilton
fuente
@IainmH, su segunda sugerencia solo cuenta el número de entradas en el directorio actual. ¿No es lo que se pretendía? (o pedido por el OP)
The Archetypal Paul
@IainMH: eso es lo que wc hace de todos modos (leer el archivo, contar el final de línea).
PhiLho
@PhiLho Tendrías que usar el interruptor -l para contar las líneas. (¿No? - ha pasado un tiempo)
Iain Holder
@Paul: por supuesto, tienes razón al 100%. Mi única defensa es que publiqué eso antes de mi café. Estoy tan afilado como un botón ahora. : D
Iain Holder
0

La única forma de saber cuántas líneas hay en el archivo es contarlas. Por supuesto, puede crear una métrica a partir de sus datos para obtener una longitud promedio de una línea y luego obtener el tamaño del archivo y dividirlo con prom. longitud pero eso no será exacto.

Esko
fuente
1
Un voto negativo interesante, no importa qué herramienta de línea de comandos esté usando, todos HACEN LO MISMO de todos modos, solo internamente. No hay una forma mágica de calcular el número de líneas, deben contarse a mano. Claro que se puede guardar como metadatos, pero esa es otra historia ...
Esko
0

Mejor código optimizado para archivos de varias líneas que no tienen carácter de nueva línea ('\ n') en 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;
}
Pramod Yadav
fuente
0

Escáner con expresiones regulares:

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

No lo he marcado.

usuario176692
fuente
-2

si usas esto

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

no puede correr a grandes filas numéricas, le gustan las filas de 100K, porque el retorno de reader.getLineNumber es int. necesita un tipo de datos largo para procesar filas máximas.

Faisal
fuente
14
Un intpuede contener valores de hasta, aproximadamente, 2 mil millones. Si está cargando un archivo con más de 2 mil millones de líneas, tiene un problema de desbordamiento. Dicho esto, si está cargando un archivo de texto no indexado con más de dos mil millones de líneas, probablemente tenga otros problemas.
Adam Norberg