关于java:如何以有效的方式获取文件中的行数?

How can I get the count of line in a file in an efficient way?

本问题已经有最佳答案,请猛点这里访问。

我有一个大文件。 它包括大约3.000-20.000行。 如何使用Java获取文件中的行总数?


1
2
3
4
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
int lines = 0;
while (reader.readLine() != null) lines++;
reader.close();

更新:为了回答这里提出的性能问题,我做了一个测量。第一件事:20.000行太少,以使程序运行一段时间。我创建了一个包含500万行的文本文件。这个解决方案(从没有像-server或-XX-options这样的参数的java开始)在我的盒子上需要大约11秒。与wc -l(UNIX命令行工具计数行)相同,为11秒。读取每个字符并寻找' n'的解决方案需要104秒,9-10倍。


Files.lines

使用Files.lines使用NIO时,Java 8+有一个非常好的简短方法。

1
2
Path path = Paths.get("./big_file.txt");
long lineCount = Files.lines(path).count();

UTF-8中的默认字符编码。您可以指定备用编码以匹配您的特定数据文件。


使用LineNumberReader

就像是

1
2
3
4
5
6
7
8
9
10
11
12
13
public static int countLines(File aFile) throws IOException {
    LineNumberReader reader = null;
    try {
        reader = new LineNumberReader(new FileReader(aFile));
        while ((reader.readLine()) != null);
        return reader.getLineNumber();
    } catch (Exception ex) {
        return -1;
    } finally {
        if(reader != null)
            reader.close();
    }
}


我找到了一些解决方案,它可能对你有用

下面是代码片段,计算文件中的no.of行。

1
2
3
4
5
  File file = new File("/mnt/sdcard/abc.txt");
  LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(file));
  lineNumberReader.skip(Long.MAX_VALUE);
  int lines = lineNumberReader.getLineNumber();
  lineNumberReader.close();


这大约和它可以获得的效率一样,缓冲二进制读取,没有字符串转换,

1
2
3
4
5
6
7
8
9
10
11
12
FileInputStream stream = new FileInputStream("/tmp/test.txt");
byte[] buffer = new byte[8192];
int count = 0;
int n;
while ((n = stream.read(buffer)) > 0) {
    for (int i = 0; i < n; i++) {
        if (buffer[i] == '
'
) count++;
    }
}
stream.close();
System.out.println("Number of lines:" + count);

读取文件并计算换行符的数量。使用Java读取文件的简单方法是java.util.Scanner类,一次一行。


您需要精确的行数还是仅需要近似值?我碰巧并行处理大文件,通常我不需要知道确切的行数 - 然后我恢复采样。将文件拆分为10个1MB块并计算每个块中的行数,然后将其乘以10,您将获得非常好的行计数近似值。


在具有1380万行的文件上测试时,此解决方案比最高评级答案快3.6倍。它只是将字节读入缓冲区并计算
字符。您可以使用缓冲区大小,但在我的机器上,任何超过8KB的内容都不会使代码更快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private int countLines(File file) throws IOException {
    int lines = 0;

    FileInputStream fis = new FileInputStream(file);
    byte[] buffer = new byte[BUFFER_SIZE]; // BUFFER_SIZE = 8 * 1024
    int read;

    while ((read = fis.read(buffer)) != -1) {
        for (int i = 0; i < read; i++) {
            if (buffer[i] == '
'
) lines++;
        }
    }

    fis.close();

    return lines;
}


快速而肮脏,但它完成了工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;

public class Counter {

    public final static void main(String[] args) throws IOException {
        if (args.length > 0) {
            File file = new File(args[0]);
            System.out.println(countLines(file));
        }
    }

    public final static int countLines(File file) throws IOException {
        ProcessBuilder builder = new ProcessBuilder("wc","-l", file.getAbsolutePath());
        Process process = builder.start();
        InputStream in = process.getInputStream();
        LineNumberReader reader = new LineNumberReader(new InputStreamReader(in));
        String line = reader.readLine();
        if (line != null) {
            return Integer.parseInt(line.trim().split("")[0]);
        } else {
            return -1;
        }
    }

}


之前的所有答案都建议读取整个文件并计算您在执行此操作时找到的换行数量。你评价一些"无效",但这是你能做到的唯一方法。"行"不是文件中的简单字符。要计算该字符,您必须查看文件中的每个字符。

对不起,你别无选择。 :-)


如果已经发布的答案不够快,您可能需要寻找特定于您的特定问题的解决方案。

例如,如果这些文本文件是仅附加到的日志,并且您经常需要知道其中的行数,则可以创建索引。此索引将包含文件中的行数,上次修改文件的时间以及文件的大小。这将允许您通过跳过已经看过的所有行并只读取新行来重新计算文件中的行数。


尝试使用unix"wc"命令。我不是故意使用它,我的意思是下载源代码并看看它们是如何做到的。它可能在c中,但您可以轻松地将行为移植到java。制作自己的问题是考虑结束cr / lf问题。


旧帖子,但我有一个可以为下一个人提供帮助的解决方案。
为什么不直接使用文件长度来了解进展情况?当然,行必须几乎相同的大小,但它适用于大文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws IOException {
    File file = new File("yourfilehere");
    double fileSize = file.length();
    System.out.println("=======> File size =" + fileSize);
    InputStream inputStream = new FileInputStream(file);
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"iso-8859-1");
    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    int totalRead = 0;
    try {
        while (bufferedReader.ready()) {
            String line = bufferedReader.readLine();
            // LINE PROCESSING HERE
            totalRead += line.length() + 1; // we add +1 byte for the newline char.
            System.out.println("Progress ===>" + ((totalRead / fileSize) * 100) +" %");
        }
    } finally {
        bufferedReader.close();
    }
}

它允许在不对文件进行任何完整读取的情况下查看进度。我知道这取决于很多元素,但我希望它会有用:)。

[版]
这是一个估计时间的版本。我把一些SYSO用于显示进度和估算。我看到你处理足够的线后你有一个很好的时间估计误差(我尝试使用10M线,并且在1%的治疗后,时间估计精确到95%)。
我知道,有些值必须在变量中设置。这段代码写得很快,但对我来说很有用。希望它也适合你:)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
long startProcessLine = System.currentTimeMillis();
    int totalRead = 0;
    long progressTime = 0;
    double percent = 0;
    int i = 0;
    int j = 0;
    int fullEstimation = 0;
    try {
        while (bufferedReader.ready()) {
            String line = bufferedReader.readLine();
            totalRead += line.length() + 1;
            progressTime = System.currentTimeMillis() - startProcessLine;
            percent = (double) totalRead / fileSize * 100;
            if ((percent > 1) && i % 10000 == 0) {
                int estimation = (int) ((progressTime / percent) * (100 - percent));
                fullEstimation += progressTime + estimation;
                j++;
                System.out.print("Progress ===>" + percent +" %");
                System.out.print(" - current progress :" + (progressTime) +" milliseconds");
                System.out.print(" - Will be finished in ===>" + estimation +" milliseconds");
                System.out.println(" - estimated full time =>" + (progressTime + estimation));
            }
            i++;
        }
    } finally {
        bufferedReader.close();
    }
    System.out.println("Ended in" + (progressTime) +" seconds");
    System.out.println("Estimative average ===>" + (fullEstimation / j));
    System.out.println("Difference:" + ((((double) 100 / (double) progressTime)) * (progressTime - (fullEstimation / j))) +"%");

如果您认为这是一个很好的解决方案,请随意改进此代码。


逐行读取文件并为每行增加一个计数器,直到您读完整个文件。


在我的测试中,其他答案在118.5k行文件上需要~150-300ms。
以下需要1ms,但只是近似值(报告117k行),并且取决于每条线具有相似的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void countSize(File file) {
  long fileLength = file.length();
  BufferedReader reader = null;
  try {
    reader = new BufferedReader(new FileReader(file));
    //Skip header as it is of different size
    reader.readLine();
    String text = reader.readLine();
    int lineLength = text.length();
    long lines = fileLength / lineLength;
    System.out.println(lines);
  } catch(IOException e) {
    e.printStackTrace();
  } finally {
    if(reader != null) {
      try {
        reader.close();
      } catch(IOException e) {
        //no-op
      }
    }
  }
}

纯Java中最快的解决方案可能是使用NIO Channel将文件作为字节读入大型ByteBuffer。然后根据相关的行分隔符约定,使用您对文件编码方案的了解,对编码的CR和/或NL字节进行计数。

最大化吞吐量的关键是:

  • 确保以大块读取文件,
  • 避免将字节从一个缓冲区复制到另一个缓冲区
  • 避免将字节复制/转换为字符,以及
  • 避免分配对象来表示文件行。

实际的代码太复杂了,我无法动态编写。此外,OP并不是要求最快的解决方案。


缓冲的阅读器过度

1
2
3
4
5
6
7
8
9
10
11
Reader r = new FileReader("f.txt");

int count = 0;
int nextchar = 0;
while (nextchar != -1){
        nextchar = r.read();
        if (nextchar == Character.getNumericValue('
'
) ){
            count++;
        }
    }

我对一个简单例子的搜索创建了一个实际上很差的例子。对单个字符重复调用read()不是最佳的。请参阅此处的示例和测量。