【问题标题】:How can I read a large text file line by line using Java?如何使用 Java 逐行读取大型文本文件?
【发布时间】:2011-08-17 14:58:10
【问题描述】:

我需要使用 Java 逐行读取大约 5-6 GB 的大文本文件。

我怎样才能快速做到这一点?

【问题讨论】:

  • @kamaci 等。人。此问题不应标记为重复。 “快速读取最后一行”不是一种选择,“逐行读取文本文件的最快方法”是否是值得商榷的。做某事最快的方法不一定是常用的方法。此外,下面的答案包括代码,您列出的最相关的替代方案没有。这个问题很有用。它目前是“java read file line by line”的最高谷歌搜索结果。最后,到达堆栈溢出并发现每 2 个问题中有 1 个被标记为待处理。
  • Here 是六种可能实现的速度比较。
  • 事件虽然我一直在阅读 cmets 认为 SO 的关闭政策很糟糕,但 SO 仍然坚持。想要不惜一切代价避免冗余是一种狭隘的开发人员视角!随它去吧!奶油会升到顶部,而 sh*t 会自行沉到底部。即使之前可能已经问过一个问题(哪个问题不是??),但这并不意味着一个新问题可能无法更好地表达它,获得更好的答案,在搜索引擎中排名更高等。有趣的是,这个问题现在“受保护”....
  • 令人难以置信的是,仅阅读标题就会将问题标记为重复。
  • 经过 Shog 的编辑,这确实是 stackoverflow.com/q/5800361/103167 的复制品,但这个已经获得了更多的活动。

标签: java performance file-io io garbage-collection


【解决方案1】:

您可以按如下方式逐行读取文件数据:

String fileLoc = "fileLocationInTheDisk";

List<String> lines = Files.lines(Path.of(fileLoc), StandardCharsets.UTF_8).collect(Collectors.toList());

【讨论】:

  • 你意识到你会在内存中存储 5-6GB 的行吗?这很可能会导致内存溢出异常。此外,OP 要求它尽快完成,这也没有回答,因为逐行处理会更有效率
【解决方案2】:

一旦 Java 8 发布(2014 年 3 月),您就可以使用流了:

try (Stream<String> lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
  lines.forEachOrdered(line -> process(line));
}

打印文件中的所有行:

try (Stream<String> lines = Files.lines(file, Charset.defaultCharset())) {
  lines.forEachOrdered(System.out::println);
}

【讨论】:

  • 使用StandardCharsets.UTF_8,为简洁起见使用Stream&lt;String&gt;,避免使用forEach(),尤其是forEachOrdered(),除非有原因。
  • 为什么要避免使用 forEach()?不好吗?
  • 如果我使用 forEach 而不是 forEachOrdered,那么这些行可能会乱序打印,不是吗?
  • @steventrouble 看看:stackoverflow.com/questions/16635398/… 如果你传递一个像forEach(this::process) 这样的简短函数引用还不错,但是如果你在forEach() 中将代码块编写为lambdas,它会变得很难看。
  • @msayag,你说得对,你需要forEachOrdered 才能按顺序执行。请注意,在这种情况下,您将无法并行化流,尽管我发现除非文件有数千行,否则并行化不会打开。
【解决方案3】:

Java 9:

try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
    stream.forEach(System.out::println);
}

【讨论】:

  • 我认为你必须System.getProperty("os.name").equals("Linux")
  • 不要将字符串与== 进行比较!
  • 这是规范的 Java 8 示例,正如其他人已经发布的那样。为什么说这是“Java-9”?
  • @Holger 忘记提及的内存映射文件可能是?
  • 要逐行处理,您可以尝试 (Stream stream = Files.lines(Paths.get(inputFile))) { stream.forEach((line) -> { System. out.println(line); }); }
【解决方案4】:

你也可以使用Apache Commons IO:

File file = new File("/home/user/file.txt");
try {
    List<String> lines = FileUtils.readLines(file);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

【讨论】:

  • FileUtils.readLines(file) 是一种已弃用的方法。此外,该方法调用 IOUtils.readLines,它使用 BufferedReader 和 ArrayList。这不是逐行的方法,当然也不是读取几 GB 的实用方法。
【解决方案5】:

用于使用 Java 8 读取文件

package com.java.java8;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

/**
 * The Class ReadLargeFile.
 *
 * @author Ankit Sood Apr 20, 2017
 */
public class ReadLargeFile {

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(String[] args) {
        try {
            Stream<String> stream = Files.lines(Paths.get("C:\\Users\\System\\Desktop\\demoData.txt"));
            stream.forEach(System.out::println);
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

【讨论】:

    【解决方案6】:

    通过使用 org.apache.commons.io 包,它提供了更高的性能,尤其是在使用 Java 6 及更低版本的遗留代码中。

    Java 7 的 API 更好,异常更少 处理和更有用的方法:

    LineIterator lineIterator = null;
    try {
        lineIterator = FileUtils.lineIterator(new File("/home/username/m.log"), "windows-1256"); // The second parameter is optionnal
        while (lineIterator.hasNext()) {
            String currentLine = lineIterator.next();
            // Some operation
        }
    }
    finally {
        LineIterator.closeQuietly(lineIterator);
    }
    

    Maven

    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
    

    【讨论】:

      【解决方案7】:

      在 Java 8 中,还有一个使用 Files.lines() 的替代方法。如果您的输入源不是文件而是更抽象的文件,例如 ReaderInputStream,您可以通过 BufferedReaders lines() 方法流式传输这些行。

      例如:

      try (BufferedReader reader = new BufferedReader(...)) {
        reader.lines().forEach(line -> processLine(line));
      }
      

      将为BufferedReader 读取的每个输入行调用processLine()

      【讨论】:

        【解决方案8】:

        看看这个博客:

        可以指定缓冲区大小,或者 可以使用默认大小。这 默认值对于大多数人来说足够大 目的。

        // Open the file
        FileInputStream fstream = new FileInputStream("textfile.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(fstream));
        
        String strLine;
        
        //Read File Line By Line
        while ((strLine = br.readLine()) != null)   {
          // Print the content on the console
          System.out.println (strLine);
        }
        
        //Close the input stream
        fstream.close();
        

        【讨论】:

        • 我的文件是 1.5 Gig,无法使用您的答案读取文件!
        • @AboozarRajabi 当然有可能。此代码可以读取任何文本文件。
        • 因链接质量差而被否决。有一个完全没有意义的DataInputStream,并且关闭了错误的流。 Java教程没有错,也不需要像这样随意引用第三方互联网垃圾。
        • 我会放弃 cmets,你有 4 行 100% 冗余的 cmets 用于 6 行代码。
        【解决方案9】:

        一个常见的模式是使用

        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = br.readLine()) != null) {
               // process the line.
            }
        }
        

        如果您假设没有字符编码,您可以更快地读取数据。例如ASCII-7,但不会有太大区别。您对数据的处理很可能需要更长的时间。

        编辑:一种不太常见的模式,可以避免line 泄漏的范围。

        try(BufferedReader br = new BufferedReader(new FileReader(file))) {
            for(String line; (line = br.readLine()) != null; ) {
                // process the line.
            }
            // line is not visible here.
        }
        

        更新:在 Java 8 中你可以这样做

        try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
                stream.forEach(System.out::println);
        }
        

        注意:您必须将 Stream 放在 try-with-resource 块中以确保在其上调用 #close 方法,否则在 GC 稍后执行之前,底层文件句柄永远不会关闭。

        【讨论】:

        • 这种模式在适当的异常处理后会是什么样子?我注意到 br.close() 会引发 IOException,这似乎令人惊讶——无论如何,当关闭打开以供读取的文件时会发生什么? FileReader 的构造函数可能会抛出 FileNotFound 异常。
        • 如果我有一个 200MB 的文件并且它可以以 90MB/s 的速度读取,那么我预计它需要大约 3 秒?使用这种“缓慢”的阅读方式,我的似乎需要几分钟。我使用的是 SSD,所以读取速度应该不是问题?
        • @JiewMeng 所以我怀疑你正在做的其他事情需要时间。您可以尝试仅读取文件的行,而 nothing 其他。
        • 为什么不 for(String line = br.readLine(); line != null; line = br.readLine()) 顺便说一句,在 Java 8 中你可以做到 try( Stream&lt;String&gt; lines = Files.lines(...) ){ for( String line : (Iterable&lt;String&gt;) lines::iterator ) { ... } } 这很难不讨厌。
        • @AleksandrDubinsky Java 8 中的闭包问题是,它很容易使代码阅读起来更加复杂(而且速度变慢),我可以看到很多开发人员过度使用它,因为它是“酷”。
        【解决方案10】:

        这是一个示例,其中包含完整的错误处理并支持 Java 7 之前的字符集规范。在 Java 7 中,您可以使用 try-with-resources 语法,这使得代码更简洁。

        如果您只想要默认字符集,您可以跳过 InputStream 并使用 FileReader。

        InputStream ins = null; // raw byte-stream
        Reader r = null; // cooked reader
        BufferedReader br = null; // buffered for readLine()
        try {
            String s;
            ins = new FileInputStream("textfile.txt");
            r = new InputStreamReader(ins, "UTF-8"); // leave charset out for default
            br = new BufferedReader(r);
            while ((s = br.readLine()) != null) {
                System.out.println(s);
            }
        }
        catch (Exception e)
        {
            System.err.println(e.getMessage()); // handle exception
        }
        finally {
            if (br != null) { try { br.close(); } catch(Throwable t) { /* ensure close happens */ } }
            if (r != null) { try { r.close(); } catch(Throwable t) { /* ensure close happens */ } }
            if (ins != null) { try { ins.close(); } catch(Throwable t) { /* ensure close happens */ } }
        }
        

        这是带有完整错误处理的 Groovy 版本:

        File f = new File("textfile.txt");
        f.withReader("UTF-8") { br ->
            br.eachLine { line ->
                println line;
            }
        }
        

        【讨论】:

        • 由字符串提供的ByteArrayInputStream 与读取大文本文件有什么关系?
        • 绝对没用的关闭。关闭每个流的理由为零。如果您关闭其中任何一个流,您会自动关闭所有其他流...
        【解决方案11】:

        我记录并测试了10 different ways to read a file in Java,然后通过让它们读取 1KB 到 1GB 的测试文件来相互运行它们。以下是读取 1GB 测试文件最快的 3 种文件读取方法。

        请注意,在运行性能测试时,我没有向控制台输出任何内容,因为这确实会减慢测试速度。我只是想测试一下原始阅读速度。

        1) java.nio.file.Files.readAllBytes()

        在 Java 7、8、9 中测试。总体而言,这是最快的方法。读取 1GB 文件的时间始终不到 1 秒。

        import java.io..File;
        import java.io.IOException;
        import java.nio.file.Files;
        
        public class ReadFile_Files_ReadAllBytes {
          public static void main(String [] pArgs) throws IOException {
            String fileName = "c:\\temp\\sample-1GB.txt";
            File file = new File(fileName);
        
            byte [] fileBytes = Files.readAllBytes(file.toPath());
            char singleChar;
            for(byte b : fileBytes) {
              singleChar = (char) b;
              System.out.print(singleChar);
            }
          }
        }
        

        2) java.nio.file.Files.lines()

        这已在 Java 8 和 9 中成功测试,但由于缺乏对 lambda 表达式的支持,它无法在 Java 7 中运行。读取一个 1GB 的文件大约需要 3.5 秒,就读取更大的文件而言,它排在第二位。

        import java.io.File;
        import java.io.IOException;
        import java.nio.file.Files;
        import java.util.stream.Stream;
        
        public class ReadFile_Files_Lines {
          public static void main(String[] pArgs) throws IOException {
            String fileName = "c:\\temp\\sample-1GB.txt";
            File file = new File(fileName);
        
            try (Stream linesStream = Files.lines(file.toPath())) {
              linesStream.forEach(line -> {
                System.out.println(line);
              });
            }
          }
        }
        

        3) 缓冲读取器

        经测试可在 Java 7、8、9 中运行。读取 1GB 测试文件大约需要 4.5 秒。

        import java.io.BufferedReader;
        import java.io.FileReader;
        import java.io.IOException;
        
        public class ReadFile_BufferedReader_ReadLine {
          public static void main(String [] args) throws IOException {
            String fileName = "c:\\temp\\sample-1GB.txt";
            FileReader fileReader = new FileReader(fileName);
        
            try (BufferedReader bufferedReader = new BufferedReader(fileReader)) {
              String line;
              while((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
              }
            }
          }
        

        你可以找到所有10种文件读取方法的完整排名here

        【讨论】:

        • 你的导游很棒:)
        • 你在这里主要是在计时System.out.print/println();您还假设在前两种情况下该文件将适合内存。
        • 很公平。也许我可以在我的回答中更明确地做出这些假设。
        • 要求逐行阅读的问题,只有最后一种方法符合...
        【解决方案12】:

        您可以使用此代码:

        import java.io.BufferedReader;
        import java.io.File;
        import java.io.FileReader;
        import java.io.IOException;
        
        public class ReadTextFile {
        
            public static void main(String[] args) throws IOException {
        
                try {
        
                    File f = new File("src/com/data.txt");
        
                    BufferedReader b = new BufferedReader(new FileReader(f));
        
                    String readLine = "";
        
                    System.out.println("Reading file using Buffered Reader");
        
                    while ((readLine = b.readLine()) != null) {
                        System.out.println(readLine);
                    }
        
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
            }
        
        }
        

        【讨论】:

        • 解释一下。
        【解决方案13】:

        您可以使用流更精确地做到这一点:

        Files.lines(Paths.get("input.txt")).forEach(s -> stringBuffer.append(s);
        

        【讨论】:

        • 我同意这实际上很好。猜想,人们不喜欢它是因为奇怪的 StringBuffer 选择(通常首选 StringBuilder,即使它可能只是变量的坏名称)。也因为上面已经提到了。
        【解决方案14】:
        BufferedReader br;
        FileInputStream fin;
        try {
            fin = new FileInputStream(fileName);
            br = new BufferedReader(new InputStreamReader(fin));
        
            /*Path pathToFile = Paths.get(fileName);
            br = Files.newBufferedReader(pathToFile,StandardCharsets.US_ASCII);*/
        
            String line = br.readLine();
            while (line != null) {
                String[] attributes = line.split(",");
                Movie movie = createMovie(attributes);
                movies.add(movie);
                line = br.readLine();
            }
            fin.close();
            br.close();
        } catch (FileNotFoundException e) {
            System.out.println("Your Message");
        } catch (IOException e) {
            System.out.println("Your Message");
        }
        

        它对我有用。希望对你也有帮助。

        【讨论】:

          【解决方案15】:

          FileReader 不会让你指定编码,如果你需要指定它,请改用InputStreamReader

          try {
              BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "Cp1252"));         
          
              String line;
              while ((line = br.readLine()) != null) {
                  // process the line.
              }
              br.close();
          
          } catch (IOException e) {
              e.printStackTrace();
          }
          

          如果您从 Windows 导入此文件,它可能具有 ANSI 编码 (Cp1252),因此您必须指定编码。

          【讨论】:

            【解决方案16】:

            您需要在class BufferedReader 中使用readLine() 方法。 从该类中创建一个新对象并对他操作此方法并将其保存为字符串。

            BufferReader Javadoc

            【讨论】:

            • 似乎指向 BufferReaderAPI 的链接已损坏
            【解决方案17】:

            实现这一目标的明确方法,

            例如:

            如果您的当前目录中有dataFile.txt

            import java.io.*;
            import java.util.Scanner;
            import java.io.FileNotFoundException;
            
            public class readByLine
            {
                public readByLine() throws FileNotFoundException
                {
                    Scanner linReader = new Scanner(new File("dataFile.txt"));
            
                    while (linReader.hasNext())
                    {
                        String line = linReader.nextLine();
                        System.out.println(line);
                    }
                    linReader.close();
            
                }
            
                public static void main(String args[])  throws FileNotFoundException
                {
                    new readByLine();
                }
            }
            

            输出如下,

            【讨论】:

            • 为什么更清晰?并且不要在这里发布文字图片。发布文本。
            • 您发布了一张图片。是文字的图片。您可以将文本直接剪切并粘贴到此页面中。没有人说任何关于发布程序的事情。发布文字图片是在浪费您的时间,我不在乎,而且浪费您的带宽,我愿意。
            【解决方案18】:

            您可以做的是使用扫描仪扫描整个文本并逐行浏览文本。 当然你应该导入以下内容:

            import java.io.File;
            import java.io.FileNotFoundException;
            import java.util.Scanner;
            public static void readText throws FileNotFoundException {
                Scanner scan = new Scanner(new File("samplefilename.txt"));
                while(scan.hasNextLine()){
                    String line = scan.nextLine();
                    //Here you can manipulate the string the way you want
                }
            }
            

            扫描仪基本上扫描所有文本。 while循环用于遍历整个文本。

            .hasNextLine() 函数是一个布尔值,如果文本中还有更多行,则返回 true。 .nextLine() 函数将整行作为字符串提供给您,然后您可以按照您想要的方式使用它。尝试System.out.println(line) 打印文本。

            旁注:.txt 是文件类型文本。

            【讨论】:

            • 不应该把方法声明改成这样:'public static void readText throws FileNotFoundException(){' Like: 'public static void readText() throws FileNotFoundException{'
            • 这比BufferedReader.readLine()慢很多,他要求性能最好的方法。
            【解决方案19】:

            我通常会直接进行阅读:

            void readResource(InputStream source) throws IOException {
                BufferedReader stream = null;
                try {
                    stream = new BufferedReader(new InputStreamReader(source));
                    while (true) {
                        String line = stream.readLine();
                        if(line == null) {
                            break;
                        }
                        //process line
                        System.out.println(line)
                    }
                } finally {
                    closeQuiet(stream);
                }
            }
            
            static void closeQuiet(Closeable closeable) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (IOException ignore) {
                    }
                }
            }
            

            【讨论】:

              【解决方案20】:

              在 Java 8 中,您可以这样做:

              try (Stream<String> lines = Files.lines (file, StandardCharsets.UTF_8))
              {
                  for (String line : (Iterable<String>) lines::iterator)
                  {
                      ;
                  }
              }
              

              一些注意事项:Files.lines 返回的流(与大多数流不同)需要关闭。由于mentioned here 的原因,我避免使用forEach()。奇怪的代码(Iterable&lt;String&gt;) lines::iterator 将 Stream 转换为 Iterable。

              【讨论】:

              • 由于没有实现Iterable,这段代码确实是,虽然有用。它需要一个演员(即(Iterable&lt;String&gt;))才能工作。
              • 如何用这种方法跳过第一行?
              • @qed for(String line : (Iterable&lt;String&gt;) lines.skip(1)::iterator)
              • 如果您不打算实际使用Stream 功能,请使用Files.newBufferedReader 而不是Files.lines 并重复调用readLine() 直到null 而不是使用像(Iterable&lt;String&gt;) lines::iterator 这样的构造要简单得多……
              • 为什么在lines::iterator中使用::?我知道 :: 的唯一用法是将方法名称打包到 lambda 函数中。在 for 循环参数之后 : 应该是可变的,而您使用 :: 获得一些 lambda 方法
              【解决方案21】:

              在 Java 7 中:

              String folderPath = "C:/folderOfMyFile";
              Path path = Paths.get(folderPath, "myFileName.csv"); //or any text file eg.: txt, bat, etc
              Charset charset = Charset.forName("UTF-8");
              
              try (BufferedReader reader = Files.newBufferedReader(path , charset)) {
                while ((line = reader.readLine()) != null ) {
                  //separate all csv fields into string array
                  String[] lineVariables = line.split(","); 
                }
              } catch (IOException e) {
                  System.err.println(e);
              }
              

              【讨论】:

              • 注意!如果字段包含逗号并且被引号包围,则以这种方式使用 line.split 将无法正确解析。此拆分将忽略这一点,并使用内部逗号将字段分隔成块。 HTH,马塞洛。
              • CSV:逗号分隔值文件,因此您不应在 csv 字段中使用逗号,除非您打算添加另一个字段。因此,在解析 CSV 文件时,在 java 中使用 split 作为逗号标记是完全正确的
              • 迭戈,这是不正确的。唯一的 CSV 标准 (RFC 4180) 明确规定“包含换行符 (CRLF)、双引号和逗号的字段应该用双引号括起来。”
              • 使用StandardCharsets.UTF_8避免Charset.forName("UTF-8")中的检查异常
              • 感谢“Diego Duarte”的评论;我必须说我同意“serg.nechaev”的回复。我“一直”看到逗号嵌入在 csv 文件中。人们期望这将被接受。恕我直言。也非常感谢“serg.nechaev”。恕我直言,你是对的。祝大家好运。
              【解决方案22】:

              你可以使用 Scanner 类

              Scanner sc=new Scanner(file);
              sc.nextLine();
              

              【讨论】:

              • @Tim 'Bomb horribly' 不是我在 CS 中认识的术语。你到底是什么意思?
              • 陷入困境,执行非常缓慢,很可能崩溃。我可能应该避免在这个网站上使用成语;)
              • @Tim 为什么要这样做?
              • 使用Scanner 很好,但这个答案不包括正确使用它的完整代码。
              • @Tim 此代码既不会“可怕地轰炸”,也不会“陷入困境”,也不会“执行非常缓慢”,也不会“最有可能崩溃”。事实上,正如所写的那样,它只会读一行,几乎是瞬间的。尽管BufferedReader.readLine() 的速度肯定是后者的几倍,但您可以通过这种方式每秒读取兆字节。如果您不这么认为,请提供您的理由。
              猜你喜欢
              • 2020-03-10
              • 2020-11-12
              • 2011-12-22
              • 1970-01-01
              • 2014-06-13
              • 1970-01-01
              相关资源
              最近更新 更多