在Java中读取纯文本文件

1052

似乎在Java中有不同的方法来读写文件数据。

我想从文件中读取ASCII数据,可能的方法有哪些,它们之间有什么区别?


27
我不同意将其关闭为“不具建设性”,但幸运的是,它很可能会被关闭为重复问题。在如何从文件内容创建字符串?将文件读入字符串的最简单方法是什么?用于读取文件的最简单类是什么?中有很好的答案。 - Jonik
1
不使用循环: {{{ Scanner sc = new Scanner(file, "UTF-8"); sc.useDelimiter("$^"); // 正则表达式匹配空字符 String text = sc.next(); sc.close(); }}} - Aivar
3
Python 中没有类似于 "read()" 的东西,可以将整个文件读取为一个字符串,这真的很有趣。 - kommradHomer
2
这是最简单的方法:http://www.mkyong.com/java/how-to-read-file-from-java-bufferedreader-example/ - deldev
31个回答

748

我最喜欢读取小文件的方法是使用BufferedReader和StringBuilder。这种方法非常简单直接(虽然不特别有效,但对于大多数情况来说足够好了):

BufferedReader br = new BufferedReader(new FileReader("file.txt"));
try {
    StringBuilder sb = new StringBuilder();
    String line = br.readLine();

    while (line != null) {
        sb.append(line);
        sb.append(System.lineSeparator());
        line = br.readLine();
    }
    String everything = sb.toString();
} finally {
    br.close();
}

有人指出在Java 7之后,你应该使用try-with-resources(自动关闭)特性:

try(BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    StringBuilder sb = new StringBuilder();
    String line = br.readLine();

    while (line != null) {
        sb.append(line);
        sb.append(System.lineSeparator());
        line = br.readLine();
    }
    String everything = sb.toString();
}

当我读到这样的字符串时,通常我想要对每行进行一些字符串处理,所以我会选择这种实现方式。

但是如果我只想将文件读入一个字符串中,我总是使用Apache Commons IO 中的IOUtils.toString() 方法。你可以在这里查看源代码:

http://www.docjar.com/html/api/org/apache/commons/io/IOUtils.java.html

FileInputStream inputStream = new FileInputStream("foo.txt");
try {
    String everything = IOUtils.toString(inputStream);
} finally {
    inputStream.close();
}

使用Java 7甚至更简单:

try(FileInputStream inputStream = new FileInputStream("foo.txt")) {     
    String everything = IOUtils.toString(inputStream);
    // do something with everything string
}

7
我对代码做了小的调整,以防止在到达最后一行时添加新行符(\n)。code while (line != null) { sb.append(line); line = br.readLine(); // 当curline不是最后一行时才添加新行符.. if(line != null) { sb.append("\n"); } }`code` - Ramon Fincken
2
类似于Apache Common IO IOUtils#toString(),sun.misc.IOUtils#readFully()也包含在Sun / Oracle JRE中。 - gb96
4
为了性能,始终首选调用sb.append('\n')而不是sb.append("\n"),因为将char附加到StringBuilder比将String附加更快。 - gb96
2
FileReader 可能会抛出 FileNotFoundException 异常,而 BufferedRead 可能会抛出 IOException 异常,因此您必须捕获它们。 - kamaci
4
Java7内置了读取整个文件或所有行的方法,不需要直接使用读取器或 ioutils。请参见http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllBytes(java.nio.file.Path)和http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllLines(java.nio.file.Path,%20java.nio.charset.Charset)。请注意,这些方法会返回文件的字节数组或字符串列表。 - kritzikratzi
显示剩余8条评论

635

ASCII是一个文本文件,因此您需要使用Readers进行读取。Java还支持使用InputStreams从二进制文件中读取。如果要读取的文件很大,则建议在FileReader之上使用BufferedReader以提高读取性能。

请参阅这篇文章,了解如何使用Reader

我还建议您下载并阅读这本精彩的(免费)书,名为Thinking In Java

在Java 7中:

new String(Files.readAllBytes(...))

(文档) 或者

Files.readAllLines(...)

(文档)

在Java 8中:

Files.lines(..).forEach(...)

(文档)


19
选择读取器主要取决于文件内容的需求。如果文件规模较小且需要全部内容,则使用FileReader并读取所有内容(或至少足够大的块)速度更快(我们进行了基准测试:1.8-2倍)。如果您逐行处理文件,则选择BufferedReader。 - Vlad
4
使用“Files.lines(..).forEach(...)”时,是否会保留行顺序?我的理解是,在此操作之后,行顺序将是任意的。 - Daniil Shevelev
47
Files.lines(…).forEach(…)不保证行的顺序,但会并行执行。如果行的顺序很重要,可以使用Files.lines(…).forEachOrdered(…),应该会保留顺序(没有验证过)。 - Palec
2
@ Palec,这很有趣,但是您能从文档中引用一下它在哪里说 Files.lines(...).forEach(...) 是并行执行的吗? 我认为只有在使用 Files.lines(...).parallel().forEach(...) 明确将流设置为并行时才会这样。 - Klitos Kyriacou
5
我的原始表述并不完全严谨,@KlitosKyriacou。重点是forEach不能保证任何顺序,这是为了方便并行处理。如果要保留顺序,请使用forEachOrdered - Palec
显示剩余4条评论

152

最简单的方法是在Java中使用Scanner类和FileReader对象。以下是一个简单的示例:

Scanner in = new Scanner(new FileReader("filename.txt"));

Scanner有多个读取字符串、数字等内容的方法,您可以在Java文档页面上查找更多信息。

例如,将整个内容读入String中:

StringBuilder sb = new StringBuilder();
while(in.hasNext()) {
    sb.append(in.next());
}
in.close();
outString = sb.toString();

如果您需要特定的编码,可以使用以下代码而不是FileReader

new InputStreamReader(new FileInputStream(fileUtf8), StandardCharsets.UTF_8)

29
当 (in.hasNext()) 时, { 打印输出(in.next()); } - Gene Bo
17
比起使用BufferedReader更容易使用。 - Jesus Ramos
3
必须使用try-catch将其包围。 - Rahal Kanishka
@JesusRamos 不是很确定,你为什么这样认为?这个方法比 while ((line = br.readLine()) != null) { sb.append(line); } 有什么更简单的地方吗? - user207421

126

这里有一个简单的解决方案:

String content = new String(Files.readAllBytes(Paths.get("sample.txt")));

或者按列表阅读:

List<String> content = Files.readAllLines(Paths.get("sample.txt"))

4
@Nery Jr,优雅简洁。 - Mahmoud Saleh
3
最好的和最简单的。 - Pengguna
readAllLines 需要 Android O(>= 8.0)。 - toto_tata
相关答案(相同解决方案):https://dev59.com/jWYq5IYBdhLWcg3w0T1y#14169729 - Nor.Z

57

以下是另一种不使用外部库的方法:

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public String readFile(String filename)
{
    String content = null;
    File file = new File(filename); // For example, foo.txt
    FileReader reader = null;
    try {
        reader = new FileReader(file);
        char[] chars = new char[(int) file.length()];
        reader.read(chars);
        content = new String(chars);
        reader.close();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(reader != null){
            reader.close();
        }
    }
    return content;
}

10
或者使用“try-with-resources” *try(FileReader reader = new FileReader(file))*。 (翻译说明:这句话已经很简单了,我只是把它翻译成了中文,并保留了原始代码。) - Hernán Eche
3
我注意到了 file.length() 这个方法,它在处理UTF-16文件时能正常工作吗? - Wayne
5
该技术假设read()会填充缓冲区;字符数等于字节数;字节数适合内存并适合整数。-1 - user207421
1
@HermesTrismegistus,我提供了四个错误的原因。StefanReich 完全正确地同意我的观点。 - user207421

44
我必须对不同的方式进行基准测试。简言之,最快的方法是使用普通的BufferedInputStream覆盖FileInputStream。如果必须读取多个文件,则使用三个线程将总执行时间减少到大约一半,但添加更多线程会逐渐降低性能,使其需要比仅使用一个线程长三倍的时间才能完成二十个线程。
假设您必须读取文件并对其内容执行有意义的操作。在这里的示例中,正在从日志中读取行并计算包含超过某个阈值的值的行数。因此,我假设单行Java 8 Files.lines(Paths.get(“/path/to/file.txt”))。map(line -> line.split(“;”)) 不是选项。
我在Java 1.8,Windows 7和SSD和HDD驱动器上进行了测试。
我编写了六种不同的实现:
rawParse:使用BufferedInputStream覆盖FileInputStream,然后按字节剪切行。这优于任何其他单线程方法,但对于非ASCII文件可能非常不方便。 lineReaderParse:使用BufferedReader覆盖FileReader,逐行读取,通过调用String.split()分割行。这大约比rawParse慢20%。 lineReaderParseParallel:这与lineReaderParse相同,但它使用多个线程。总体而言,这是所有情况下最快的选项。
nioFilesParse:使用java.nio.files.Files.lines()
nioAsyncParse:使用带有完成处理程序和线程池的AsynchronousFileChannel。
nioMemoryMappedParse:使用内存映射文件。这真的是一个坏主意,执行时间至少比任何其他实现长三倍。
这些是在四核i7和SSD驱动器上读取204个每个4 MB的文件的平均时间。文件是即时生成的,以避免磁盘缓存。
rawParse                11.10 sec
lineReaderParse         13.86 sec
lineReaderParseParallel  6.00 sec
nioFilesParse           13.52 sec
nioAsyncParse           16.06 sec
nioMemoryMappedParse    37.68 sec

我发现使用SSD或HDD驱动器运行时的差异比我预期的小,SSD大约快15%。这可能是因为文件在未碎片化的HDD上生成,并且它们是按顺序读取的,因此旋转驱动器几乎可以像SSD一样执行。
我对nioAsyncParse实现的性能低下感到惊讶。无论我是以错误的方式实现了某些内容,还是使用NIO和完成处理程序的多线程实现与使用java.io API的单线程实现(甚至更糟)。此外,使用CompletionHandler的异步解析要比直接在旧流上进行实现的代码行数长得多,而且实现起来也更加棘手。
现在是六个实现,后面跟着一个包含它们所有的类以及一个可参数化的main()方法,可以在其中调整文件数、文件大小和并发度。请注意,文件大小变化范围约为20%。这是为了避免由于所有文件大小完全相同而导致的任何影响。
rawParse
public void rawParse(final String targetDir, final int numberOfFiles) throws IOException, ParseException {
    overrunCount = 0;
    final int dl = (int) ';';
    StringBuffer lineBuffer = new StringBuffer(1024);
    for (int f=0; f<numberOfFiles; f++) {
        File fl = new File(targetDir+filenamePreffix+String.valueOf(f)+".txt");
        FileInputStream fin = new FileInputStream(fl);
        BufferedInputStream bin = new BufferedInputStream(fin);
        int character;
        while((character=bin.read())!=-1) {
            if (character==dl) {

                // Here is where something is done with each line
                doSomethingWithRawLine(lineBuffer.toString());
                lineBuffer.setLength(0);
            }
            else {
                lineBuffer.append((char) character);
            }
        }
        bin.close();
        fin.close();
    }
}

public final void doSomethingWithRawLine(String line) throws ParseException {
    // What to do for each line
    int fieldNumber = 0;
    final int len = line.length();
    StringBuffer fieldBuffer = new StringBuffer(256);
    for (int charPos=0; charPos<len; charPos++) {
        char c = line.charAt(charPos);
        if (c==DL0) {
            String fieldValue = fieldBuffer.toString();
            if (fieldValue.length()>0) {
                switch (fieldNumber) {
                    case 0:
                        Date dt = fmt.parse(fieldValue);
                        fieldNumber++;
                        break;
                    case 1:
                        double d = Double.parseDouble(fieldValue);
                        fieldNumber++;
                        break;
                    case 2:
                        int t = Integer.parseInt(fieldValue);
                        fieldNumber++;
                        break;
                    case 3:
                        if (fieldValue.equals("overrun"))
                            overrunCount++;
                        break;
                }
            }
            fieldBuffer.setLength(0);
        }
        else {
            fieldBuffer.append(c);
        }
    }
}

lineReaderParse

public void lineReaderParse(final String targetDir, final int numberOfFiles) throws IOException, ParseException {
    String line;
    for (int f=0; f<numberOfFiles; f++) {
        File fl = new File(targetDir+filenamePreffix+String.valueOf(f)+".txt");
        FileReader frd = new FileReader(fl);
        BufferedReader brd = new BufferedReader(frd);

        while ((line=brd.readLine())!=null)
            doSomethingWithLine(line);
        brd.close();
        frd.close();
    }
}

public final void doSomethingWithLine(String line) throws ParseException {
    // Example of what to do for each line
    String[] fields = line.split(";");
    Date dt = fmt.parse(fields[0]);
    double d = Double.parseDouble(fields[1]);
    int t = Integer.parseInt(fields[2]);
    if (fields[3].equals("overrun"))
        overrunCount++;
}

lineReaderParseParallel

public void lineReaderParseParallel(final String targetDir, final int numberOfFiles, final int degreeOfParalelism) throws IOException, ParseException, InterruptedException {
    Thread[] pool = new Thread[degreeOfParalelism];
    int batchSize = numberOfFiles / degreeOfParalelism;
    for (int b=0; b<degreeOfParalelism; b++) {
        pool[b] = new LineReaderParseThread(targetDir, b*batchSize, b*batchSize+b*batchSize);
        pool[b].start();
    }
    for (int b=0; b<degreeOfParalelism; b++)
        pool[b].join();
}

class LineReaderParseThread extends Thread {

    private String targetDir;
    private int fileFrom;
    private int fileTo;
    private DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private int overrunCounter = 0;

    public LineReaderParseThread(String targetDir, int fileFrom, int fileTo) {
        this.targetDir = targetDir;
        this.fileFrom = fileFrom;
        this.fileTo = fileTo;
    }

    private void doSomethingWithTheLine(String line) throws ParseException {
        String[] fields = line.split(DL);
        Date dt = fmt.parse(fields[0]);
        double d = Double.parseDouble(fields[1]);
        int t = Integer.parseInt(fields[2]);
        if (fields[3].equals("overrun"))
            overrunCounter++;
    }

    @Override
    public void run() {
        String line;
        for (int f=fileFrom; f<fileTo; f++) {
            File fl = new File(targetDir+filenamePreffix+String.valueOf(f)+".txt");
            try {
            FileReader frd = new FileReader(fl);
            BufferedReader brd = new BufferedReader(frd);
            while ((line=brd.readLine())!=null) {
                doSomethingWithTheLine(line);
            }
            brd.close();
            frd.close();
            } catch (IOException | ParseException ioe) { }
        }
    }
}

nioFilesParse

public void nioFilesParse(final String targetDir, final int numberOfFiles) throws IOException, ParseException {
    for (int f=0; f<numberOfFiles; f++) {
        Path ph = Paths.get(targetDir+filenamePreffix+String.valueOf(f)+".txt");
        Consumer<String> action = new LineConsumer();
        Stream<String> lines = Files.lines(ph);
        lines.forEach(action);
        lines.close();
    }
}


class LineConsumer implements Consumer<String> {

    @Override
    public void accept(String line) {

        // What to do for each line
        String[] fields = line.split(DL);
        if (fields.length>1) {
            try {
                Date dt = fmt.parse(fields[0]);
            }
            catch (ParseException e) {
            }
            double d = Double.parseDouble(fields[1]);
            int t = Integer.parseInt(fields[2]);
            if (fields[3].equals("overrun"))
                overrunCount++;
        }
    }
}

nioAsyncParse

public void nioAsyncParse(final String targetDir, final int numberOfFiles, final int numberOfThreads, final int bufferSize) throws IOException, ParseException, InterruptedException {
    ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(numberOfThreads);
    ConcurrentLinkedQueue<ByteBuffer> byteBuffers = new ConcurrentLinkedQueue<ByteBuffer>();

    for (int b=0; b<numberOfThreads; b++)
        byteBuffers.add(ByteBuffer.allocate(bufferSize));

    for (int f=0; f<numberOfFiles; f++) {
        consumerThreads.acquire();
        String fileName = targetDir+filenamePreffix+String.valueOf(f)+".txt";
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get(fileName), EnumSet.of(StandardOpenOption.READ), pool);
        BufferConsumer consumer = new BufferConsumer(byteBuffers, fileName, bufferSize);
        channel.read(consumer.buffer(), 0l, channel, consumer);
    }
    consumerThreads.acquire(numberOfThreads);
}


class BufferConsumer implements CompletionHandler<Integer, AsynchronousFileChannel> {

        private ConcurrentLinkedQueue<ByteBuffer> buffers;
        private ByteBuffer bytes;
        private String file;
        private StringBuffer chars;
        private int limit;
        private long position;
        private DateFormat frmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        public BufferConsumer(ConcurrentLinkedQueue<ByteBuffer> byteBuffers, String fileName, int bufferSize) {
            buffers = byteBuffers;
            bytes = buffers.poll();
            if (bytes==null)
                bytes = ByteBuffer.allocate(bufferSize);

            file = fileName;
            chars = new StringBuffer(bufferSize);
            frmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            limit = bufferSize;
            position = 0l;
        }

        public ByteBuffer buffer() {
            return bytes;
        }

        @Override
        public synchronized void completed(Integer result, AsynchronousFileChannel channel) {

            if (result!=-1) {
                bytes.flip();
                final int len = bytes.limit();
                int i = 0;
                try {
                    for (i = 0; i < len; i++) {
                        byte by = bytes.get();
                        if (by=='\n') {
                            // ***
                            // The code used to process the line goes here
                            chars.setLength(0);
                        }
                        else {
                                chars.append((char) by);
                        }
                    }
                }
                catch (Exception x) {
                    System.out.println(
                        "Caught exception " + x.getClass().getName() + " " + x.getMessage() +
                        " i=" + String.valueOf(i) + ", limit=" + String.valueOf(len) +
                        ", position="+String.valueOf(position));
                }

                if (len==limit) {
                    bytes.clear();
                    position += len;
                    channel.read(bytes, position, channel, this);
                }
                else {
                    try {
                        channel.close();
                    }
                    catch (IOException e) {
                    }
                    consumerThreads.release();
                    bytes.clear();
                    buffers.add(bytes);
                }
            }
            else {
                try {
                    channel.close();
                }
                catch (IOException e) {
                }
                consumerThreads.release();
                bytes.clear();
                buffers.add(bytes);
            }
        }

        @Override
        public void failed(Throwable e, AsynchronousFileChannel channel) {
        }
};

所有情况的完整可运行实现

https://github.com/sergiomt/javaiobenchmark/blob/master/FileReadBenchmark.java


28

这里有三种经过验证的可行方法:

使用BufferedReader

package io;
import java.io.*;
public class ReadFromFile2 {
    public static void main(String[] args)throws Exception {
        File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
        BufferedReader br = new BufferedReader(new FileReader(file));
        String st;
        while((st=br.readLine()) != null){
            System.out.println(st);
        }
    }
}

使用Scanner

package io;

import java.io.File;
import java.util.Scanner;

public class ReadFromFileUsingScanner {
    public static void main(String[] args) throws Exception {
        File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
        Scanner sc = new Scanner(file);
        while(sc.hasNextLine()){
            System.out.println(sc.nextLine());
        }
    }
}

使用 FileReader

package io;
import java.io.*;
public class ReadingFromFile {

    public static void main(String[] args) throws Exception {
        FileReader fr = new FileReader("C:\\Users\\pankaj\\Desktop\\test.java");
        int i;
        while ((i=fr.read()) != -1){
            System.out.print((char) i);
        }
    }
}

使用Scanner类在不使用循环的情况下读取整个文件

package io;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ReadingEntireFileWithoutLoop {

    public static void main(String[] args) throws FileNotFoundException {
        File file = new File("C:\\Users\\pankaj\\Desktop\\test.java");
        Scanner sc = new Scanner(file);
        sc.useDelimiter("\\Z");
        System.out.println(sc.next());
    }
}

1
如果文件夹在项目内,如何指定路径? - Kavipriya
2
java.nio.file.Files 怎么样?现在我们可以直接使用 readAllLinesreadAllByteslines - Claude Martin

20

org.apache.commons.io.FileUtils 中的方法也可能非常方便,例如:

/**
 * Reads the contents of a file line by line to a List
 * of Strings using the default encoding for the VM.
 */
static List readLines(File file)

或者如果您更喜欢Guava(一种更现代、积极维护的库),它在其Files类中具有类似的实用程序。此答案中的简单示例 - Jonik
1
或者您可以直接使用内置方法获取所有行:http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllLines(java.nio.file.Path,%20java.nio.charset.Charset) - kritzikratzi
Apache Commons 的链接似乎已经失效了。 - kebs

18

我记录了15种在Java中读取文件的方法,并针对不同大小的文件-从1 KB到1 GB进行了速度测试,以下是前三种最佳方法:

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

    经过测试,在Java 7、8和9中均有效。

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-10KB.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);
    }
  }
}
  • java.io.BufferedReader.readLine()

    在Java 7、8、9中测试可用。

  • 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-10KB.txt";
        FileReader fileReader = new FileReader(fileName);
    
        try (BufferedReader bufferedReader = new BufferedReader(fileReader)) {
          String line;
          while((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
          }
        }
      }
    }
    
  • java.nio.file.Files.lines()

    这个方法在Java 8和9中测试过可以运行,但是因为需要lambda表达式,在Java 7中无法运行。

  • 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-10KB.txt";
        File file = new File(fileName);
    
        try (Stream linesStream = Files.lines(file.toPath())) {
          linesStream.forEach(line -> {
            System.out.println(line);
          });
        }
      }
    }
    

    17

    您想对这段文本做什么?该文件大小是否足够小,以适合放入内存中?我建议您尝试找到最简单的方式来处理符合您需求的文件。FileUtils库非常适合处理此类任务。

    for(String line: FileUtils.readLines("my-text-file"))
        System.out.println(line);
    

    2
    它也内置于Java7中:http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllLines(java.nio.file.Path,%20java.nio.charset.Charset) - kritzikratzi
    @PeterLawrey可能指的是org.apache.commons.io.FileUtils。Google链接可能会随着最广泛的含义转变而改变内容,但这符合他的查询并且看起来正确。 - Palec
    2
    不幸的是,现在没有 readLines(String) 方法了,而 readLines(File) 已经被弃用,推荐使用 readLines(File, Charset) 方法。编码也可以作为字符串提供。 - Palec

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接