在Java中读取纯文本文件

似乎有不同的方式来读取和写入数据的文件在Java。

我想从文件中读取ASCII数据。可能的方法和它们的区别是什么?

2584405 次浏览

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

通过这篇文章了解如何使用Reader

我还建议你下载并阅读这本名为Java思考的精彩(但免费)的书

Java7

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

(文档)

Files.readAllLines(...)

(文档)

Java8

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

(文档)

最简单的方法是在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)

我最喜欢的阅读小文件的方法是使用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();}

有些人指出,在Java7之后,您应该使用资源试用(即自动关闭)功能:

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

当我读这样的字符串时,我通常想对每行做一些字符串处理,所以我选择这个实现。

虽然如果我想实际上将文件读入String,我总是使用Apache共享资源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();}

更简单的Java7:

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

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)

到目前为止,我还没有在其他答案中看到它。但是,如果“最佳”意味着速度,那么新的JavaI/O(NIO)可能会提供最快的性能,但并不总是最容易为学习的人弄清楚。

您想如何处理文本?文件是否足够小以适应内存?我会尝试找到最简单的方法来处理您需要的文件。FileUtils库非常适合此操作。

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

我编写的这段代码对于非常大的文件来说要快得多:

public String readDoc(File f) {String text = "";int read, N = 1024 * 1024;char[] buffer = new char[N];
try {FileReader fr = new FileReader(f);BufferedReader br = new BufferedReader(fr);
while(true) {read = br.read(buffer, 0, N);text += new String(buffer, 0, read);
if(read < N) {break;}}} catch(Exception ex) {ex.printStackTrace();}
return text;}

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

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

可能不如缓冲I/O快,但非常简洁:

    String content;try (Scanner scanner = new Scanner(textFile).useDelimiter("\\Z")) {content = scanner.next();}

\Z模式告诉Scanner分隔符是EOF。

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

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

或者读为列表:

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

在Java中,从文件中读取数据的最简单方法是使用文件类读取文件,使用扫描仪类读取文件的内容。

public static void main(String args[])throws Exception{File f = new File("input.txt");takeInputIn2DArray(f);}
public static void takeInputIn2DArray(File f) throws Exception{Scanner s = new Scanner(f);int a[][] = new int[20][20];for(int i=0; i<20; i++){for(int j=0; j<20; j++){a[i][j] = s.nextInt();}}}

PS:不要忘记导入java.util.*;扫描仪工作。

对于基于JSF的Maven Web应用程序,只需使用ClassLoader和Resources文件夹即可读取您想要的任何文件:

  1. 将您要读取的任何文件放在Resources文件夹中。
  2. 将Apache Commons IO依赖项放入您的POM:

    <dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency>
  3. Use the code below to read it (e.g. below is reading in a .json file):

    String metadata = null;FileInputStream inputStream;try {
    ClassLoader loader = Thread.currentThread().getContextClassLoader();inputStream = (FileInputStream) loader.getResourceAsStream("/metadata.json");metadata = IOUtils.toString(inputStream);inputStream.close();}catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return metadata;

You can do the same for text files, .properties files, XSD schemas, etc.

使用BufferedReader:

import java.io.BufferedReader;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;
BufferedReader br;try {br = new BufferedReader(new FileReader("/fileToRead.txt"));try {String x;while ( (x = br.readLine()) != null ) {// Printing out each line in the fileSystem.out.println(x);}}catch (IOException e) {e.printStackTrace();}}catch (FileNotFoundException e) {System.out.println(e);e.printStackTrace();}

这基本上与Jesus Ramos的答案完全相同,除了文件而不是文件读取器加上迭代来逐步遍历文件的内容。

Scanner in = new Scanner(new File("filename.txt"));
while (in.hasNext()) { // Iterates each line in the fileString line = in.nextLine();// Do something with line}
in.close(); // Don't forget to close resource leaks

…抛出FileNotFoundException

如果这是关于结构的简单性,请使用Java吻

import static kiss.API.*;
class App {void run() {String line;try (Close in = inOpen("file.dat")) {while ((line = readLine()) != null) {println(line);}}}}

番石榴为此提供了一个单行代码:

import com.google.common.base.Charsets;import com.google.common.io.Files;
String contents = Files.toString(filePath, Charsets.UTF_8);

我必须对不同的方法进行基准测试。我将对我的发现发表评论,但简而言之,最快的方法是在FileInputStream上使用普通的旧BufferedInputStream。如果必须读取许多文件,那么三个线程将使总执行时间减少大约一半,但添加更多线程将逐渐降低性能,直到使用20个线程完成所需的时间是仅使用一个线程的三倍。

假设您必须读取一个文件并对其内容做一些有意义的事情。在这里的示例中,是从日志中读取行并计算包含超过某个阈值的值的行。所以我假设单行Java8Files.lines(Paths.get("/path/to/file.txt")).map(line -> line.split(";"))不是一个选项。

我在Java1.8,Windows 7以及SSD和HDD驱动器上进行了测试。

我写了六个不同的实现:

rawParse:在FileInputStream上使用BufferedInputStream,然后逐个字节读取行。这优于任何其他单线程方法,但对于非ASCII文件可能非常不方便。

读队列:在FileReader上使用BufferedReader,逐行读取,通过调用String.split()拆分行。这比rawParse慢了大约20%。

行阅读器并行:这与lineReaderParse相同,但它使用多个线程。这是所有情况下最快的选项。

nioFilesParse:使用java.nio.files.Files.lines()

nioAsyncParse:使用带有完成处理程序和线程池的异步FileChannel。

nioMemoryMapapetParse对应的接口名称:使用内存映射文件。这确实是一个坏主意,执行时间至少比任何其他实现长三倍。

这些是在四核i7和SSD驱动器上读取每个4 MB的204个文件的平均时间。这些文件是动态生成的,以避免磁盘缓存。

rawParse                11.10 seclineReaderParse         13.86 seclineReaderParseParallel  6.00 secnioFilesParse           13.52 secnioAsyncParse           16.06 secnioMemoryMappedParse    37.68 sec

我发现在SSD或HDD驱动器上运行的差异小于我预期的SSD大约快15%。这可能是因为文件是在未碎片的HDD上生成的,并且它们是按顺序读取的,因此旋转驱动器几乎可以像SSD一样执行。

我对nioAsyncParse实现的低性能感到惊讶。要么我以错误的方式实现了一些东西,要么使用NIO和完成处理程序的多线程实现与使用java.ioAPI的单线程实现执行相同(甚至更差)。此外,带有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 linedoSomethingWithRawLine(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 lineint 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);}}}

读队列

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 lineString[] 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++;}

行阅读器并行

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++;}
@Overridepublic 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> {
@Overridepublic void accept(String line) {
// What to do for each lineString[] 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;}
@Overridepublic 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 herechars.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);}}
@Overridepublic void failed(Throwable e, AsynchronousFileChannel channel) {}};

所有case的完整可运行执行

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

下面是以Java8方式执行此操作的一行代码。假设text.txt文件位于Eclipse项目目录的根目录中。

Files.lines(Paths.get("text.txt")).collect(Collectors.toList());

以下是三种工作和测试方法:

使用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());}}

这可能不是问题的确切答案。这只是另一种读取文件的方式,您不需要在Java代码中显式指定文件的路径,而是将其作为命令行参数读取。

使用以下代码,

import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.IOException;
public class InputReader{
public static void main(String[] args)throws IOException{BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String s="";while((s=br.readLine())!=null){System.out.println(s);}}}

只需继续运行它:

java InputReader < input.txt

这将读取input.txt的内容并将其打印到您的控制台。

您还可以通过命令行使您的System.out.println()写入特定文件,如下所示:

java InputReader < input.txt > output.txt

这将从input.txt读取并写入output.txt

仙人掌给你一个声明式单行代码:

new TextOf(new File("a.txt")).asString();

您可以使用readAllLines和join方法在一行中获取整个文件内容:

String str = String.join("\n",Files.readAllLines(Paths.get("e:\\text.txt")));

它默认使用UTF-8编码,可以正确读取ASCII数据。

你也可以使用readAllBytes:

String str = new String(Files.readAllBytes(Paths.get("e:\\text.txt")), StandardCharsets.UTF_8);

我认为readAllBytes更快,更精确,因为它不会用\n替换新行,也可能是新行\r\n。这取决于您的需求,哪一个适合。

我记录了在Java中读取文件的15种方法,然后用各种文件大小测试了它们的速度-从1 KB到1 GB,以下是实现此目的的三种方法:

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

    测试工作在Java7、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);}}}
  2. java.io.BufferedReader.readLine()

    Tested to work in 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);}}}}
  3. java.nio.file.Files.lines()

    This was tested to work in Java 8 and 9 but won't work in Java 7 because of the lambda expression requirement.

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

缓冲流类在实践中性能更高,以至于NIO.2 API包含专门返回这些流类的方法,部分原因是鼓励您始终在应用程序中使用缓冲流。

下面是一个例子:

Path path = Paths.get("/myfolder/myfile.ext");try (BufferedReader reader = Files.newBufferedReader(path)) {// Read from the streamString currentLine = null;while ((currentLine = reader.readLine()) != null)//do your code here} catch (IOException e) {// Handle file I/O exception...}

您可以替换此代码

BufferedReader reader = Files.newBufferedReader(path);

BufferedReader br = new BufferedReader(new FileReader("/myfolder/myfile.ext"));

我推荐这个文章来学习JavaNIO和IO的主要用途。

import java.util.stream.Stream;import java.nio.file.*;import java.io.*;
class ReadFile {
public static void main(String[] args) {
String filename = "Test.txt";
try(Stream<String> stream = Files.lines(Paths.get(filename))) {
stream.forEach(System.out:: println);
} catch (IOException e) {
e.printStackTrace();}
}
}

只需使用Java 8 Stream。

try {File f = new File("filename.txt");Scanner r = new Scanner(f);while (r.hasNextLine()) {String data = r.nextLine();JOptionPane.showMessageDialog(data);}r.close();} catch (FileNotFoundException ex) {JOptionPane.showMessageDialog("Error occurred");ex.printStackTrace();}

最直观的方法介绍在Java11Files.readString

import java.io.*;import java.nio.file.Files;import java.nio.file.Paths;
public class App {public static void main(String args[]) throws IOException {String content = Files.readString(Paths.get("D:\\sandbox\\mvn\\my-app\\my-app.iml"));System.out.print(content);}}

PHP几十年来一直是这个奢侈品!☺

如果您有一个大文件,您可以使用Apache Commons IO迭代处理文件,而不会耗尽可用内存。

try (LineIterator it = FileUtils.lineIterator(theFile, "UTF-8")) {while (it.hasNext()) {String line = it.nextLine();// do something with line}}
try (Stream<String> stream = Files.lines(Paths.get(String.valueOf(new File("yourFile.txt"))))) {stream.forEach(System.out::println);} catch (IOException e) {e.printStackTrace();}

new File(<path_name>)

通过将给定的路径名字符串转换为抽象路径名来创建新的File实例。如果给定字符串为空字符串,则结果为空抽象路径名。参数:路径名-路径名字符串抛出:NullPointerException-如果路径名参数为null

Files.lines返回一个字符串流

Stream<String> stream = Files.lines(Paths.get(String.valueOf(new File("yourFile.txt"))))可以抛出nullPointerExc的,FileNotFoundException,所以,保持它里面try会照顾异常在运行时

stream.forEach(System.out::println);

这用于遍历流并在控制台中打印如果您有不同的用例,您可以提供客户函数来操作行流

我最喜欢的新方法是简单地从BufferedReader输入中读取整个文本文件:

String text = input.lines().collect(Collectors.joining(System.lineSeparator())));

这将通过在每一行后面添加新行(line分隔符)来读取整个文件。没有分隔符,它将把所有行连接在一起。这似乎是自Java8以来一直存在的。