web-dev-qa-db-ja.com

Javaのファイルの行数

私は巨大なデータファイルを使いますが、時々これらのファイルの行数を知るだけでよく、通常それらを開いてファイルの終わりに達するまで一行ずつ読みます。

それをするより賢い方法があるかどうか私は思っていました

204
Mark

これは私がこれまでに見つけた最速のバージョンで、readLinesの約6倍の速さです。 150MBのログファイルでは、readLines()を使用した場合の2.40秒に対して、0.35秒かかります。ただの楽しみのために、linuxのwc -lコマンドは0.15秒かかります。

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

9年半後の編集:私はほとんどJavaの経験がありませんが、誰もやっていないことに悩まされたので、とにかく以下のLineNumberReadername__ソリューションに対してこのコードをベンチマークしようとしました。特に大きなファイルの場合、私の解決策は早いようです。オプティマイザが適切な仕事をするまでには数回かかるようです。私はこのコードを少し試してみたところ、一貫して最速の新しいバージョンを作成しました。

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

ベンチマークの結果は1.3GBのテキストファイルで、y軸は秒です。同じファイルで100回実行し、それぞれの実行をSystem.nanoTime()で測定しました。 countLinesOldname__にはいくつかの異常値があり、countLinesNewname__には異常値がないことがわかりますが、少し速いですが、違いは統計的に有意です。 LineNumberReadername__は明らかに遅いです。

Benchmark Plot

227
martinus

この問題に対する別の解決策を実装しました。行数を数えるほうがより効率的です。

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

承認された回答は、改行で終わらない複数行のファイルに対して1エラーずつずれています。改行なしで終わる1行のファイルは1を返しますが、改行なしで終わる2行のファイルは1も返します。これはこれを修正する承認されたソリューションの実装です。 endsWithoutNewLineチェックは最終的な読み込み以外の全てに無駄がありますが、関数全体と比べると些細な時間です。

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

Java-8 を使うと、ストリームを使うことができます。

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

ファイルの末尾に改行が含まれていない場合、上記のcount()メソッドを使用した回答で、行の誤カウントが発生しました。ファイルの最後の行のカウントに失敗しました。

この方法は私にはうまく機能します。

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

私はこれが古い質問であることを知っています、しかし受け入れられた解決策は私がそれをするのに必要としたものと全く一致しませんでした。それで、私はそれを洗練して、(単なる改行ではなく)様々な改行コードを受け入れ、(ISO-8859-ではなく)特定の文字エンコーディングを使うようにしました。n)オールインワンメソッド(必要に応じてリファクタリング):

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

この解決策は、私のテストでは約4%遅いという、一般に認められている解決策と同等の速度です(ただし、Javaのタイミングテストは信頼性が低いとされています)。

8
Nathan Ryan
/**
 * 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();
    }
}

JDK8_u31でテスト済み。しかし、実際にはこの方法に比べてパフォーマンスは遅くなります。

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

テスト済みでとても速いです。

4

私はラインを数えるために上記の方法をテストしました、そしてここに私のシステムでテストされた異なる方法のための私の所見があります

ファイルサイズ:1.6 Gb方法:

  1. スキャナーを使用する:約35秒
  2. BufferedReaderを使用:約5秒
  3. Java 8を使用して:約5秒
  4. LineNumberReaderを使用:約5秒

さらに、Java 8のアプローチはとても便利です:Files.lines(Paths.get(filePath)、Charset.defaultCharset())。count()[戻り値の型:long]

4
Anshul

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

改行を数えるwc -l:sメソッドは問題ありませんが、最後の行が改行で終わっていないファイルでは直感的ではない結果を返します。

LineNumberReaderに基づいているが行数に1を加算した@ er.vikasソリューションは、最後の行が改行で終わっているファイルに対して直感的ではない結果を返しました。

そこで私は次のように扱うアルゴを作りました。

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

そしてそれはこのようになります:

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

直感的な結果が必要な場合は、これを使用できます。 wc -lとの互換性が必要な場合は、@ er.vikasソリューションを使用してください。ただし、結果に追加しないでスキップを再試行してください。

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

Javaコード内からProcessクラスを使用するのはどうですか。そして、コマンドの出力を読みます。

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

それを試してみる必要があります。結果を掲載します。

2
Sunil Shevante

この面白い解決策は実際には本当にうまくいきます!

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

索引構造がない場合は、ファイル全体の読み取りを回避することはできません。しかし、1行ずつ読み飛ばして正規表現を使ってすべての行の終端文字と一致させるのを避けて、最適化することができます。

1
David Schmitt

Unixベースのシステムでは、コマンドラインでwcコマンドを使用します。

0
Peter Hilton

ファイルに何行あるかを知る唯一の方法はそれらを数えることです。もちろん、データからメトリックを作成して1行の平均長さを求めてからファイルサイズを取得し、それを平均で除算することができます。長さが正確ではありません。

0
Esko

正規表現付きスキャナー:

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

それをクロックしていません。

0
user176692

EOFで改行文字( '\ n')を持たない複数行ファイル用の最適化された最高のコード。

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