我使用了巨大的数据文件,有时我只需要知道这些文件中的行数,通常我会打开它们并逐行读取它们,直到到达文件末尾

我想知道如果有更聪明的方法做到这一点

#1 楼

这是我到目前为止找到的最快的版本,比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();
    }
}


EDIT,9 1/2年后:我几乎没有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.3GB文本文件的基准测试结果,y轴位于秒。我已经用同一文件执行了100次运行,并使用System.nanoTime()测量了每次运行。您会看到countLinesOld有一些离群值,而countLinesNew没有离群值,虽然速度更快一点,但差异在统计上是显着的。 LineNumberReader明显较慢。



评论


BufferedInputStream应该为您进行缓冲,因此我看不到使用中间byte []数组如何使其更快。无论如何,您不可能比重复使用readLine()更好(因为API会对其进行优化)。

– wds
09年1月17日在13:23

完成输入流后,您将要关闭它,不是吗?

– Bendin
09年5月24日18:15

如果缓冲有所帮助,那是因为BufferedInputStream默认情况下会缓冲8K。将byte []增大到此大小或更大,然后可以删除BufferedInputStream。例如尝试1024 * 1024字节。

– Peter Lawrey
09年5月24日在19:02

两件事:(1)Java源代码中的行终止符的定义是回车,换行或回车后跟换行。您的解决方案不适用于用作线路终结器的CR。当然,我可以认为唯一将CR用作默认行终止符的OS是Mac OS X之前的Mac OS。(2)您的解决方案假定使用字符编码,例如US-ASCII或UTF-8。对于诸如UTF-16之类的编码,行数可能不准确。

–内森·瑞安(Nathan Ryan)
2012年9月21日上午11:58

很棒的代码...对于400mb的文本文件,只花了一秒钟。非常感谢@martinus

–user3181500
17年11月2日,12:43

#2 楼

我已经实现了该问题的另一种解决方案,发现行计数更有效:

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长的文件吗?为什么要在这里跳过很长时间?

–epb
15年4月3日在20:27

实际上,将一加到计数是不正确的。 wc -l计算文件中换行符的数量。这行得通,因为每一行都以换行符结尾,包括文件中的最后一行。每行都有一个换行符,包括空行,因此换行符数==文件中的行数。现在,FileNumberReader中的lineNumber变量也表示看到的换行符数。它从零开始,直到找到任何换行符为止,并且随着看到的每个换行符而增加。因此,请不要在行号中添加一个。

–亚历山大·托斯汀
16年2月16日在14:06

@PB_MLT:尽管您将单行不带换行符的文件报告为0行是正确的,但这是wc -l也报告此类文件的方式。另请参阅stackoverflow.com/questions/729692/…

–亚历山大·托斯汀
16年2月16日在14:10

@PB_MLT:如果文件仅包含换行符,则会遇到相反的问题。您建议的算法将返回0,而wc -l将返回1。我得出结论,所有方法都有缺陷,并根据我希望其行为的方式实现了一个缺陷,请参见此处的其他答案。

–亚历山大·托斯汀
16年2月16日在14:50

我对此表决投了反对票,因为似乎没有人对它进行基准测试

–amstegraf
17年2月1日在19:01

#3 楼

对于不以换行符结尾的多行文件,可接受的答案有一个错误。一个没有换行符结束的一行文件将返回1,但是一个没有换行符结束的两行文件也将返回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();
    }
}


评论


接得好。不知道为什么您不只是编辑接受的答案并在注释中做笔记。到目前为止,大多数人都不会读下去。

–瑞安
2013年12月11日在21:33

@Ryan,用4个90年代以上的赞誉来编辑一个已接受4年的答案并不恰当。

–DMulligan
2013年12月12日下午6:47

@AFinkelstein,我认为这是使此网站如此出色的原因,您可以编辑投票最高的答案。

–塞巴斯蒂安
2014年1月27日在8:48

此解决方案不处理回车符(\ r)和回车符后跟换行符(\ r \ n)

–西蒙·布兰德霍夫-SonarSource
2014年2月5日下午13:36

@Simon Brandhof,我对为什么将回车视为另一行感到困惑? “ \ n”是回车换行符,所以写“ \ r \ n”的人都不了解...。此外,他正在按字符搜索char,因此我很确定是否有人使用“ \ r \ n”,它仍然会捕捉到“ \ n”并计算行数。无论哪种方式,我认为他都说得很对。但是,在许多情况下,这不是获得行数的足够方法。

– nckbrz
2014年4月8日在3:46



#4 楼

使用Java-8,您可以使用流:

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


评论


代码有错误。简单,但是非常慢...尝试在下面(上)看一下我的答案。

–恩内斯塔斯·格鲁迪斯(Ernestas Gruodis)
15年2月20日在22:55

#5 楼

如果文件的末尾没有换行符,则上述方法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年1月30日8:02

[INFO] PMD失败:xx:19规则:EmptyWhileStmt优先级:3避免while语句为空。

–霍恩·埃利特(Chhorn Elit)
1月1日下午16:49

#6 楼

我知道这是一个古老的问题,但是公认的解决方案与我需要它做的工作并不完全匹配。因此,我对其进行了改进,使其可以接受各种行终止符(而不仅仅是换行符),并使用指定的字符编码(而不是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不可靠。

#7 楼

我测试了上述用于计数行的方法,这是我在系统上测试的不同方法的观察结果

文件大小:1.6 Gb
方法:



使用Scanner:大约35s

使用BufferedReader:大约5s

使用Java 8:5s大约

使用LineNumberReader:5s大约

此外,Java8方法似乎非常方便:

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


#8 楼

/**
 * 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 -消耗时间:122796351 Stream -数目行:109808方法-消耗时间:12838000方法-Num行数:1而且行数甚至也是错误的

–aw-think
2015年2月27日在12:59



我在32位计算机上进行了测试。也许在64位上会得到不同的结果。而且我记得它是10倍甚至更多倍。您能张贴文字以在某处数行吗?为了方便起见,可以使用Notepad2查看换行符。

–恩内斯塔斯·格鲁迪斯(Ernestas Gruodis)
2015年2月27日在13:01



那可能是不同的。

–aw-think
15年2月27日在13:02

如果您关心性能,则无论如何都要读入自己的缓冲区时,不应使用BufferedInputStream。此外,即使您的方法可能在性能上稍有优势,但由于它不再支持唯一的\ r行终止符(旧的MacOS),并且不支持每种编码,因此也会失去灵活性。

–霍尔格
16年11月14日在18:58

#9 楼

使用扫描仪的直接方法

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


#10 楼

我得出的结论是,wc -l:s计算换行符的方法很好,但是在最后一行不以换行符结尾的文件上返回非直观结果。

和基于LineNumberReader的@ 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();
}


#11 楼

如何在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);
}


需要尝试一下。将发布结果。

#12 楼

如果您没有任何索引结构,那么您将无法读取整个文件。但是您可以通过避免逐行读取并使用正则表达式匹配所有行终止符来对其进行优化。

评论


听起来像个好主意。任何人都尝试过并为此使用正则表达式吗?

– willcodejavaforfood
09年1月17日在11:02

我怀疑这是个好主意:它将需要立即读取整个文件(martinus避免了这种情况),而正则表达式对于这种用法(简单地搜索固定字符)而言过于矫正(并且速度较慢)。

– PhiLho
09年1月17日在11:31

@will:/ \ n /呢? @PhiLo:正则表达式执行器是经过高度调整的性能计算机。除了将所有内容读入内存的警告之外,我认为手动执行不会更快。

–David Schmitt
2011年5月17日晚上11:37

#13 楼

这个有趣的解决方案实际上真的很好!!

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


#14 楼

看来LineNumberReader可以采用几种不同的方法。
我做到了:
int lines = 0;

FileReader input = new FileReader(fileLocation);
LineNumberReader count = new LineNumberReader(input);

String line = count.readLine();

if(count.ready())
{
    while(line != null) {
        lines = count.getLineNumber();
        line = count.readLine();
    }
    
    lines+=1;
}
    
count.close();

System.out.println(lines);

更简单的是,您可以使用Java BufferedReader lines()方法返回流。元素,然后使用Stream count()方法对所有元素进行计数。然后只需在输出中添加一个即可获得文本文件中的行数。
例如:
FileReader input = new FileReader(fileLocation);
LineNumberReader count = new LineNumberReader(input);

int lines = (int)count.lines().count() + 1;
    
count.close();

System.out.println(lines);


#15 楼

在基于Unix的系统上,在命令行上使用wc命令。

评论


@IainmH,您的第二条建议只是计算当前目录中的条目数。不是想要的吗? (或OP要求)

–原型保罗
09年1月17日在10:14

@IainMH:这就是wc要做的(读取文件,计算行尾)。

– PhiLho
09年1月17日在11:29

@PhiLho您必须使用-l开关来计算行数。 (不是吗?-已经有一段时间了)

–伊恩持有人
09年1月17日在12:24

@Paul-您当然是100%正确。我唯一的辩护是我在喝咖啡前张贴了那个。我现在像按钮一样敏锐。 :D

–伊恩持有人
09年1月17日在12:25

#16 楼

知道文件中有多少行的唯一方法是对它们进行计数。当然,您可以根据数据创建一个指标,平均长度为一行,然后获取文件大小并将其除以avg。长度,但不准确。

评论


有趣的是,不管您使用哪种命令行工具,它们都只能在内部执行相同的操作。没有一种神奇的方法可以计算出行数,必须手动计算行数。当然可以将其保存为元数据,但这完全是另一回事了……

– Esko
09年1月17日在9:27

#17 楼

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


#18 楼

带有正则表达式的扫描器:

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


还没有计时。

#19 楼

如果您使用此

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


您不能运行到大数行,例如10万行,因为从reader.getLineNumber返回的是int。您需要长类型的数据来处理最大行。.

评论


一个int最多可以容纳20亿个值。如果加载的文件超过20亿行,则存在溢出问题。就是说,如果加载的未索引文本文件超过20亿行,则可能还有其他问题。

–亚当·诺伯格(Adam Norberg)
2011年6月2日21:26