it-swarm.com.de

Anzahl der Zeilen in einer Datei in Java

Ich verwende große Datendateien, manchmal muss ich nur die Anzahl der Zeilen in diesen Dateien kennen, normalerweise öffne ich sie und lese sie Zeile für Zeile, bis ich das Ende der Datei erreiche

Ich habe mich gefragt, ob es einen intelligenteren Weg gibt, das zu tun

206
Mark

Dies ist die schnellste Version, die ich bisher gefunden habe, ungefähr 6-mal schneller als readLines. Bei einer 150-MB-Protokolldatei dauert dies 0,35 Sekunden gegenüber 2,40 Sekunden bei Verwendung von readLines (). Nur zum Spaß dauert der Befehl wc -l unter Linux 0,15 Sekunden.

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

BEARBEITEN, 9 1/2 Jahre später: Ich habe praktisch keine Java Erfahrung, aber trotzdem habe ich versucht, diesen Code mit der unten stehenden LineNumberReader - Lösung zu vergleichen, da es mich störte, dass niemand Es sieht so aus, als ob meine Lösung vor allem für große Dateien schneller ist. Obwohl es ein paar Durchläufe zu dauern scheint, bis der Optimierer einen anständigen Job erledigt. Ich habe ein bisschen mit dem Code gespielt und eine neue Version erstellt, die konsistent ist am schnellsten:

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

Benchmark-Ergebnisse für eine 1,3-GB-Textdatei, y-Achse in Sekunden. Ich habe 100 Läufe mit derselben Datei durchgeführt und jeden Lauf mit System.nanoTime() gemessen. Sie können sehen, dass countLinesOld einige Ausreißer hat und countLinesNew keine und obwohl es nur ein bisschen schneller ist, ist der Unterschied statistisch signifikant. LineNumberReader ist deutlich langsamer.

Benchmark Plot

230
martinus

Ich habe eine andere Lösung für das Problem implementiert, die ich beim Zählen von Zeilen effizienter fand:

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

Die akzeptierte Antwort hat einen Fehler von eins zu eins für mehrzeilige Dateien, die nicht in Zeilenumbrüchen enden. Eine einzeilige Datei, die ohne eine neue Zeile endet, würde 1 zurückgeben, aber eine zweizeilige Datei, die ohne eine neue Zeile endet, würde auch 1 zurückgeben. Hier ist eine Implementierung der akzeptierten Lösung, die dies behebt. Die endsWithoutNewLine-Überprüfungen sind für alles außer dem endgültigen Lesen verschwenderisch, sollten aber im Vergleich zur Gesamtfunktion trivial zeitlich sein.

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

Mit Java-8 können Sie Streams verwenden:

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

Die Antwort mit der obigen Methode count () gab mir Zeilenfehler, wenn eine Datei am Ende der Datei keine neue Zeile enthielt - die letzte Zeile in der Datei konnte nicht gezählt werden.

Diese Methode funktioniert bei mir besser:

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

Ich weiß, dass dies eine alte Frage ist, aber die akzeptierte Lösung stimmte nicht genau mit der überein, für die ich sie benötigt hatte. Also habe ich es verfeinert, um verschiedene Zeilenabschlüsse zu akzeptieren (anstatt nur Zeilenvorschub) und eine bestimmte Zeichenkodierung zu verwenden (anstatt ISO-8859-n). Alles in einer Methode (Refactor nach Bedarf):

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

Diese Lösung ist in der Geschwindigkeit mit der akzeptierten Lösung vergleichbar, etwa 4% langsamer in meinen Tests (obwohl Timing-Tests in Java sind notorisch unzuverlässig).

8
Nathan Ryan

Ich habe die obigen Methoden zum Zählen von Linien getestet und hier sind meine Beobachtungen für verschiedene Methoden, die auf meinem System getestet wurden

Dateigröße: 1,6 GB Methoden:

  1. Mit Scanner: Ca.
  2. mit BufferedReader: Ca.
  3. Mit Java 8: Ca.
  4. Mit LineNumberReader: Ca.

Außerdem scheint der Java8-Ansatz recht praktisch zu sein: Files.lines (Paths.get (filePath), Charset.defaultCharset ()). Count () [Rückgabetyp: 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();
    }
}

Getestet auf JDK8_u31. Aber in der Tat ist die Leistung im Vergleich zu dieser Methode langsam:

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

Getestet und sehr schnell.

4

Ein einfacher Weg mit dem 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

Ich kam zu dem Schluss, dass wc -l: s Methode zum Zählen von Zeilenumbrüchen ist in Ordnung, gibt jedoch nicht intuitive Ergebnisse für Dateien zurück, bei denen die letzte Zeile nicht mit einem Zeilenumbruch endet.

Die auf LineNumberReader basierende @ er.vikas-Lösung, bei der jedoch eine Zeile zur Zeilenzahl hinzugefügt wurde, ergab nicht intuitive Ergebnisse für Dateien, bei denen die letzte Zeile mit newline endet.

Ich habe daher ein Algo erstellt, das wie folgt funktioniert:

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

Und so sieht es aus:

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

Wenn Sie intuitive Ergebnisse wünschen, können Sie diese verwenden. Wenn du nur wc -l Kompatibilität, verwenden Sie einfach die @ er.vikas-Lösung, aber fügen Sie dem Ergebnis keine hinzu und wiederholen Sie den Vorgang:

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

Wie wäre es, wenn Sie die Process-Klasse aus dem Code Java) heraus verwenden und dann die Ausgabe des Befehls lesen.

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

Müssen es aber versuchen. Wird die Ergebnisse veröffentlichen.

2
Sunil Shevante

Diese lustige Lösung funktioniert wirklich gut!

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

Wenn Sie keine Indexstrukturen haben, können Sie das Lesen der gesamten Datei nicht umgehen. Sie können es jedoch optimieren, indem Sie vermeiden, es zeilenweise zu lesen, und einen regulären Ausdruck verwenden, um alle Zeilenabschlusszeichen abzugleichen.

1
David Schmitt

Verwenden Sie auf Unix-basierten Systemen den Befehl wc in der Befehlszeile.

0
Peter Hilton

Um zu wissen, wie viele Zeilen sich in der Datei befinden, müssen Sie sie zählen. Sie können natürlich aus Ihren Daten eine Metrik mit einer durchschnittlichen Länge von einer Zeile erstellen und dann die Dateigröße abrufen und diese mit avg teilen. Länge, aber das wird nicht genau sein.

0
Esko

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

Ich habe es nicht getaktet.

0
user176692

Bester optimierter Code für mehrzeilige Dateien ohne Zeilenumbruchzeichen ('\ n') bei 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