回答:
これは私がこれまでに見つけた中で最も速いバージョンで、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
明らかに遅いです。
私は問題の別の解決策を実装しましたが、行を数えるのがより効率的であることがわかりました:
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
}
LineNumberReader
のlineNumber
フィールドは整数です... Integer.MAX_VALUEより長いファイルをラップするだけではありませんか?なぜここで長い間スキップするのですか?
wc -l
ファイル内の改行文字の数をカウントします。これは、ファイルの最後の行を含め、すべての行が改行で終了するため機能します。空の行を含むすべての行に改行文字があるため、改行文字の数==ファイル内の行数。これで、lineNumber
変数in FileNumberReader
は、見られる改行文字の数も表します。改行が検出される前のゼロから始まり、改行文字が見られるたびに増加します。したがって、行番号に1を追加しないでください。
wc -l
もこの種類のファイルを報告する方法です。また、stackoverflow.com
wc -l
返し、1を返します。すべてのメソッドに欠陥があると結論付け、それをどのように動作させたいかに基づいて実装しました。他の回答はこちらを参照してください。
受け入れられた回答には、改行で終わらない複数行のファイルの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();
}
}
と java-8、ストリームを使用できます。
try (Stream<String> lines = Files.lines(path, Charset.defaultCharset())) {
long numOfLines = lines.count();
...
}
上記の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;
}
cnt
。その場合、に長いデータ型を使用する柔軟性があります。
私はこれが古い質問であることを知っていますが、受け入れられた解決策は私がそれを行うために必要なものと完全には一致しませんでした。そこで、(改行だけでなく)さまざまな行終端文字を受け入れ、(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のタイミングテストは信頼性が低いことで有名です)。
/**
* 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
また、行数も間違っています
BufferedInputStream
する場合は、とにかく自分のバッファーに読み込むときにaを使用しないでください。その上、メソッドにわずかなパフォーマンス上の利点があっても、単一の\r
行終端記号(古いMacOS)をサポートしておらず、すべてのエンコーディングをサポートしていないため、柔軟性が失われます。
スキャナーを使用する簡単な方法
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);
}
私は、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();
}
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行ずつ読み取ることを避け、正規表現を使用してすべての行ターミネーターに一致させることで、最適化できます。
Unixベースのシステムでは、wc
コマンドラインでコマンドを使用します。
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;
}
正規表現付きスキャナー:
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;
}
計時していません。
これを使えば
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万行ほどの大きな行に実行できません。最大行を処理するには、長いタイプのデータが必要です。
int
約2億までの値を保持することができます。20億行を超えるファイルをロードする場合は、オーバーフローの問題があります。とはいえ、20億行を超えるインデックス付けされていないテキストファイルを読み込んでいる場合は、おそらく他の問題が発生しています。