Javaのファイルの行数


213

私は巨大なデータファイルを使用していますが、これらのファイルの行数を知るだけでよい場合があります。通常、ファイルを開いて、ファイルの最後に達するまで1行ずつ読み取ります。

それを行うためのよりスマートな方法があるかどうか私は思っていました

回答:


237

これは私がこれまでに見つけた中で最も速いバージョンで、readLinesの約6倍の速さです。150MBのログファイルでは、0.35秒かかりますが、readLines()を使用すると2.40秒かかります。面白くするために、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の経験はありませんが、とにかくLineNumberReader誰もそれをしなかったので困ったので、とにかく私はこのコードを以下の解決策に対してベンチマークしようとしました。特に大きなファイルの場合、私のソリューションの方が速いようです。オプティマイザがまともな仕事をするまで、数回実行するようですが。私はコードで少し遊んで、一貫して最速の新しいバージョンを作成しました:

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.3 GBのテキストファイルのベンチマーク結果、y軸(秒)。同じファイルで100回実行し、それぞれの実行をで測定しましたSystem.nanoTime()。これにcountLinesOldは、いくつかの外れ値があり、外れ値がcountLinesNewなく、少しだけ高速ですが、その差は統計的に有意です。LineNumberReader明らかに遅いです。

ベンチマークプロット


5
BufferedInputStreamがバッファリングを実行する必要があるため、中間のbyte []配列を使用するとどのように速くなるかわかりません。とにかく、readLine()を繰り返し使用するよりもはるかに良いことはありません(APIによって最適化されるため)。
wds

54
使い終わったら、そのInputStreamを閉じますね。
2009年

5
バッファリングが役立つ場合、BufferedInputStreamはデフォルトで8Kをバッファリングするためです。byte []をこのサイズ以上に増やすと、BufferedInputStreamをドロップできます。たとえば、1024 * 1024バイトを試してください。
Peter Lawrey、

8
2つのこと:(1)Javaソースでの行末記号の定義は、復帰、改行、または復帰とそれに続く改行です。あなたの解決策は、行末記号として使用されるCRでは機能しません。確かに、デフォルトの行末記号としてCRを使用していると私が思う唯一のOSは、Mac OS Xより前のMac OSです。(2)ソリューションは、US-ASCIIやUTF-8などの文字エンコーディングを想定しています。行数は、UTF-16などのエンコーディングでは不正確な場合があります。
ネイサンライアン

2
素晴らしいコード... 400MBのテキストファイルの場合、1秒しかかかりませんでした。@martinusに感謝
user3181500

199

私は問題の別の解決策を実装しましたが、行を数えるのがより効率的であることがわかりました:

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
}

LineNumberReaderlineNumberフィールドは整数です... Integer.MAX_VALUEより長いファイルをラップするだけではありませんか?なぜここで長い間スキップするのですか?
epb 2015

1
カウントに1を追加することは実際には正しくありません。wc -lファイル内の改行文字の数をカウントします。これは、ファイルの最後の行を含め、すべての行が改行で終了するため機能します。空の行を含むすべての行に改行文字があるため、改行文字の数==ファイル内の行数。これで、lineNumber変数in FileNumberReaderは、見られる改行文字の数も表します。改行が検出される前のゼロから始まり、改行文字が見られるたびに増加します。したがって、行番号に1を追加しないでください。
Alexander Torstling 2016

1
@PB_MLT:改行のない1行のファイルが0行として報告されることは正しいですが、これwc -lもこの種類のファイルを報告する方法です。また、stackoverflow.com
questions / 729692 /…を

@PB_MLT:ファイルが改行のみで構成されている場合、反対の問題が発生します。提案されたアルゴは0をwc -l返し、1を返します。すべてのメソッドに欠陥があると結論付け、それをどのように動作させたいかに基づいて実装しました。他の回答はこちらを参照してください。
Alexander Torstling 2016

3
誰もそれをベンチマークしていないようなので、私はこの回答に反対票を投じました
amstegraf 2017

30

受け入れられた回答には、改行で終わらない複数行のファイルの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();
    }
}

6
良いキャッチ。承認された回答を編集してコメントにメモしただけではなかった理由がわかりません。ほとんどの人はここまで読みません。
ライアン

@ライアン、90歳以上の賛成票で4年前の承認済み回答を編集するのは適切ではないと感じました。
DMulligan 2013

@AFinkelstein、私はそれがこのサイトをとても素晴らしいものにしていると私は感じています、あなたはトップ投票の回答を編集することできます。
セバスチャン

3
このソリューションは、キャリッジリターン(\ r)およびキャリッジリターンとそれに続くラインフィード(\ r \ n)を処理しません
Simon Brandhof-SonarSource

@Simon Brandhof、キャリッジリターンが別の行としてカウントされるのはなぜですか?「\ n」はキャリッジリターンラインフィードなので、「\ r \ n」と書いた人は何かを理解できません...さらに、彼は文字ごとに検索しているので、誰かが「\ r」を使用したかどうかは確かです\ n "それでも" \ n "をキャッチして行を数えます。どちらにせよ、彼は問題なく主張した。ただし、これは行数を取得するための十分な方法ではない多くのシナリオです。
nckbrz 14

22

、ストリームを使用できます。

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

1
コードにエラーがあります。シンプルですが、非常に遅いです...以下の(上記の)私の答えを見てください。
Ernestas Gruodis、2015

12

上記の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;
}

この場合、LineNumberReaderを使用する必要はありません。単にBufferedReaderを使用してくださいcnt。その場合、に長いデータ型を使用する柔軟性があります。
Syed Aqeel Ashiq 2014年

[情報] PMD失敗:xx:19ルール:EmptyWhileStmt優先度:3空のwhileステートメントを避けてください。
Chhorn Elit

8

私はこれが古い質問であることを知っていますが、受け入れられた解決策は私がそれを行うために必要なものと完全には一致しませんでした。そこで、(改行だけでなく)さまざまな行終端文字を受け入れ、(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

私は線を数えるために上記の方法をテストしました、そしてこれが私のシステムでテストされたさまざまな方法の私の観察です

ファイルサイズ:1.6 Gbメソッド:

  1. スキャナーの使用:約35秒
  2. BufferedReaderの使用:約5秒
  3. Java 8の使用:約5秒
  4. LineNumberReaderの使用:約5秒

さらに、Java8アプローチは非常に便利なようです。

Files.lines(Paths.get(filePath), Charset.defaultCharset()).count()
[Return type : long]

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

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

テスト済みで非常に高速です。


これは正しくありません。あなたのコードでいくつかの実験を行い、メソッドは常に遅いです。Stream<String> - Time consumed: 122796351 Stream<String> - Num lines: 109808 Method - Time consumed: 12838000 Method - Num lines: 1また、行数も間違っています
2015

32ビットマシンでテストしました。たぶん64ビットでは違う結果になるかもしれません。そして、私が覚えているようにそれは10倍以上の違いでした。テキストを投稿して、どこかに行を数えてもらえますか?Notepad2を使用すると、便宜上改行を表示できます。
Ernestas Gruodis、2015

それが違いかもしれません。
2015

パフォーマンスを気にBufferedInputStreamする場合は、とにかく自分のバッファーに読み込むときにaを使用しないでください。その上、メソッドにわずかなパフォーマンス上の利点があっても、単一の\r行終端記号(古いMacOS)をサポートしておらず、すべてのエンコーディングをサポートしていないため、柔軟性が失われます。
Holger、

4

スキャナーを使用する簡単な方法

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

私は、wc -l改行をカウントする:sメソッドは問題ないと結論付けましたが、最後の行が改行で終わっていないファイルでは直感的でない結果を返します。

そして、LineNumberReaderに基づく@ er.vikasソリューションですが、行カウントに1を追加すると、最後の行が改行で終わるファイルで非直感的な結果が返されました。

そのため、次のように処理するアルゴを作成しました。

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

2

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

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


1

インデックス構造がない場合は、完全なファイルの読み取りを回避できません。ただし、1行ずつ読み取ることを避け、正規表現を使用してすべての行ターミネーターに一致させることで、最適化できます。


きちんとしたアイデアのように聞こえます。誰かが試して正規表現を持っていますか?
willcodejavaforfood 2009年

1
私はそれがそのような良い考えであるとは思いません:ファイル全体を一度に読み取る必要があり(martinusはこれを回避します)、正規表現はそのような使用法(固定文字の単純な検索)に対して過剰(そして低速)です。
PhiLho 2009年

@will:/ \ n /はどうですか?@PhiLo:Regexエグゼキューターは高度にチューニングされたパフォーマンスマシンです。すべてをメモリに読み込むという警告を除いて、手動による実装の方が速くなるとは思いません。
David Schmitt、

1

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

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

0

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


@ IainmH、2番目の提案は、現在のディレクトリのエントリ数を数えるだけです。意図したものではありませんか?(またはOPからの依頼)
典型的なポール、

@IainMH:とにかくそれはwcが行うことです(ファイルの読み取り、行末のカウント)。
PhiLho 2009年

@PhiLho行を数えるには-lスイッチを使用する必要があります。(そうではありませんか?
久しぶりです

@Paul-もちろん100%正しいです。私の唯一の防御は、コーヒーの前にそれを投稿したことです。今ではボタンと同じくらいシャープです。:D
Iainホルダー

0

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


1
興味深いコマンドは、使用しているコマンドラインツールに関係なく、すべて内部的にのみ同じことを実行します。行数を計算する魔法の方法はありません。手で数える必要があります。確かにそれは、メタデータとして保存することができますが、それは全体の別の話...だ
エスコ

0

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

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

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

計時していません。


-2

これを使えば

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

reader.getLineNumberからの戻り値がintであるため、10万行ほどの大きな行に実行できません。最大行を処理するには、長いタイプのデータが必要です。


14
int約2億までの値を保持することができます。20億行を超えるファイルをロードする場合は、オーバーフローの問題があります。とはいえ、20億行を超えるインデックス付けされていないテキストファイルを読み込んでいる場合は、おそらく他の問題が発生しています。
アダムNorberg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.