Pregunta

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

¿Fue útil?

Solución

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 1/2 años después: prácticamente no tengo experiencia en Java, pero de todos modos he intentado comparar este código con la solución LineNumberReader 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 ejecuciones 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. Realicé 100 ejecuciones con el mismo archivo y medí cada ejecución con System.nanoTime () . Puede ver que countLinesOld tiene algunos valores atípicos, y countLinesNew no tiene ninguno y, aunque es solo un poco más rápido, la diferencia es estadísticamente significativa. LineNumberReader es claramente más lento.

 Parcela de referencia

Otros consejos

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
}

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

Con , puede usar transmisiones:

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

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

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 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).

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. Uso del escáner : 35 s aprox.
  2. Uso de BufferedReader : 5s aprox.
  3. Uso de Java 8 : 5s aprox.
  4. Uso de LineNumberReader : 5s aprox.

Además, el enfoque Java8 parece bastante útil: Files.lines (Paths.get (filePath), Charset.defaultCharset ()). count () [Tipo de retorno: largo]

/**
 * 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.

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

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 arrojó resultados no intuitivos en archivos donde la última línea termina con nueva línea.

Por lo tanto, hice un algoritmo 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 compatibilidad con wc -l , 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();
}

¿Qué tal usar la clase Process desde el código Java? Y luego leyendo el resultado 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);
}

Necesito probarlo sin embargo. Publicará los resultados.

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.

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

En sistemas basados ??en Unix, use el comando wc en la línea de comandos.

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.

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

Escáner con expresiones regulares:

public int getLineCount() {
    Scanner fileScanner = null;
    int lineCount = 0;
    Pattern lineEndPattern = Pattern.compile("(?m)<*>quot;);  
    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 cronometrado.

si usa 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 ejecutar filas de números grandes, le gustan las filas de 100K, porque el retorno de reader.getLineNumber es int. necesita un tipo de datos largo para procesar las filas máximas ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top