如何从文件内容创建 Java 字符串?

我已经在下面使用过一段时间了。至少在我访问过的网站上,它似乎是分布最广的。

在 Java 中,是否有更好 / 不同的方式将文件读取为字符串?

private String readFile(String file) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader (file));
    String         line = null;
    StringBuilder  stringBuilder = new StringBuilder();
    String         ls = System.getProperty("line.separator");

    try {
        while((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(ls);
        }

        return stringBuilder.toString();
    } finally {
        reader.close();
    }
}

答案

读取文件中的所有文本

Java 11 添加了readString()方法以将小的文件读取为String ,从而保留了行终止符:

String content = Files.readString(path, StandardCharsets.US_ASCII);

对于介于 Java 7 和 11 之间的版本,这是一个紧凑而健壮的习惯用法,它包装在实用程序方法中:

static String readFile(String path, Charset encoding) 
  throws IOException 
{
  byte[] encoded = Files.readAllBytes(Paths.get(path));
  return new String(encoded, encoding);
}

从文件中读取文本行

Java 7 添加了一种便捷方法,可以将文件读取为文本行,List<String> 。这种方法是 “有损的”,因为从每行的末端剥去了行分隔符。

List<String> lines = Files.readAllLines(Paths.get(path), encoding);

Java 8 添加了Files.lines()方法来生成Stream<String> 。同样,此方法是有损的,因为剥去了行分隔符。如果在读取文件时遇到IOException ,则它将包装在UncheckedIOException ,因为Stream不接受引发已检查异常的 lambda。

try (Stream<String> lines = Files.lines(path, encoding)) {
  lines.forEach(System.out::println);
}

Stream确实需要close()调用;这个在 API 上的文档很少,我怀疑很多人甚至没有注意到Streamclose()方法。确保使用如图所示的 ARM 模块。

如果您使用的不是文件源,则可以使用BufferedReaderlines()方法。

内存利用率

保留换行符的第一种方法可能临时需要占用文件大小几倍的内存,因为在短时间内,原始文件内容(字节数组)和解码后的字符(即使已编码也为 16 位) (文件中的 8 位)一次存储在内存中。将其应用于相对于可用内存较小的文件是最安全的。

第二种方法是读取行,通常可以提高内存效率,因为用于解码的输入字节缓冲区不需要包含整个文件。但是,它仍然不适用于相对于可用内存而言非常大的文件。

为了读取大文件,您需要为程序提供不同的设计,即从流中读取文本块,对其进行处理,然后再移至下一个,重新使用相同的固定大小的内存块。在此,“大” 取决于计算机规格。如今,此阈值可能是许多 GB 的 RAM。如果输入 “记录” 恰好是单独的行,则使用Stream<String>的第三种方法是执行此操作的一种方法。 (使用BufferedReaderreadLine()方法等效于此方法。)

字符编码

原始帖子的示例中缺少的一件事是字符编码。在某些特殊情况下,平台默认值是您想要的,但是很少见,您应该可以证明自己的选择合理。

StandardCharsets类为所有 Java 运行时所需的编码定义了一些常量:

String content = readFile("test.txt", StandardCharsets.UTF_8);

该平台默认值可从Charset本身获得:

String content = readFile("test.txt", Charset.defaultCharset());

注意:此答案在很大程度上替代了我的 Java 6 版本。 Java 7 的实用程序安全地简化了代码,并且使用映射字节缓冲区的旧答案阻止了读取的文件被删除,直到对映射缓冲区进行垃圾回收为止。您可以通过此答案上的 “已编辑” 链接查看旧版本。

如果您愿意使用外部库,请查看Apache Commons IO (200KB JAR)。它包含一个org.apache.commons.io.FileUtils.readFileToString()方法,该方法使您可以用一行代码将整个File读入String

例:

import java.io.*;
import java.nio.charset.*;
import org.apache.commons.io.*;

public String readFile() throws IOException {
    File file = new File("data.txt");
    return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
}

基于Scanner非常精简的解决方案:

Scanner scanner = new Scanner( new File("poem.txt") );
String text = scanner.useDelimiter("\\A").next();
scanner.close(); // Put this call in a finally block

或者,如果要设置字符集:

Scanner scanner = new Scanner( new File("poem.txt"), "UTF-8" );
String text = scanner.useDelimiter("\\A").next();
scanner.close(); // Put this call in a finally block

或者,使用try-with-resources块,它将为您调用scanner.close()

try (Scanner scanner = new Scanner( new File("poem.txt"), "UTF-8" )) {
    String text = scanner.useDelimiter("\\A").next();
}

请记住, Scanner构造函数可以抛出IOException 。并且不要忘记导入java.iojava.util

资料来源: Pat Niemeyer 的博客

import java.nio.file.Files;
import java.nio.file.Paths;

String content = new String(Files.readAllBytes(Paths.get("readMe.txt")), "UTF-8");

从 Java 7 开始,您可以使用这种方式。

如果您正在寻找不涉及第三方库的替代方案(例如Commons I / O ),则可以使用Scanner类:

private String readFile(String pathname) throws IOException {

    File file = new File(pathname);
    StringBuilder fileContents = new StringBuilder((int)file.length());        

    try (Scanner scanner = new Scanner(file)) {
        while(scanner.hasNextLine()) {
            fileContents.append(scanner.nextLine() + System.lineSeparator());
        }
        return fileContents.toString();
    }
}

番石榴的方法类似于 Willi aus Rohr 提到的 Commons IOUtils 的方法:

import com.google.common.base.Charsets;
import com.google.common.io.Files;

// ...

String text = Files.toString(new File(path), Charsets.UTF_8);

由 PiggyPiglet 编辑
已弃用Files#toString ,并将于 2019 年 8 月将其删除。而应使用Files.asCharSource(new File(path), StandardCharsets.UTF_8).read();

奥斯卡 · 雷耶斯(Oscar Reyes)编辑

这是引用的库上的(简化)基础代码:

InputStream in = new FileInputStream(file);
byte[] b  = new byte[file.length()];
int len = b.length;
int total = 0;

while (total < len) {
  int result = in.read(b, total, len - total);
  if (result == -1) {
    break;
  }
  total += result;
}

return new String( b , Charsets.UTF_8 );

编辑 (作者:Jonik):上面的内容与 Guava 最新版本的源代码不匹配。对于电流源,看到类文件CharStreamsByteSourceCharSourcecom.google.common.io包。

import java.nio.file.Files;

.......

String readFile(String filename) {
            File f = new File(filename);
            try {
                byte[] bytes = Files.readAllBytes(f.toPath());
                return new String(bytes,"UTF-8");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "";
    }

该代码将规范换行符,这可能是也可能不是您真正想要的。

这是一个不执行此操作的替代方法,它比 NIO 代码更易于理解(IMO)(尽管它仍然使用java.nio.charset.Charset ):

public static String readFile(String file, String csName)
            throws IOException {
    Charset cs = Charset.forName(csName);
    return readFile(file, cs);
}

public static String readFile(String file, Charset cs)
            throws IOException {
    // No real need to close the BufferedReader/InputStreamReader
    // as they're only wrapping the stream
    FileInputStream stream = new FileInputStream(file);
    try {
        Reader reader = new BufferedReader(new InputStreamReader(stream, cs));
        StringBuilder builder = new StringBuilder();
        char[] buffer = new char[8192];
        int read;
        while ((read = reader.read(buffer, 0, buffer.length)) > 0) {
            builder.append(buffer, 0, read);
        }
        return builder.toString();
    } finally {
        // Potential issue here: if this throws an IOException,
        // it will mask any others. Normally I'd use a utility
        // method which would log exceptions and swallow them
        stream.close();
    }        
}

如果您需要字符串处理(并行处理),则 Java 8 具有出色的 Stream API。

String result = Files.lines(Paths.get("file.txt"))
                    .parallel() // for parallel processing 
                    .map(String::trim) // to change line   
                    .filter(line -> line.length() > 2) // to filter some lines by a predicate                        
                    .collect(Collectors.joining()); // to join lines

可以从Oracle Java SE 8 下载页面下载的 JDK 示例sample/lambda/BulkDataOperations中提供了更多示例。

另一个班轮的例子

String out = String.join("\n", Files.readAllLines(Paths.get("file.txt")));

如果是文本文件,为什么不使用apache commons-io呢?

它具有以下方法

public static String readFileToString(File file) throws IOException

如果要将这些行作为列表使用

public static List<String> readLines(File file) throws IOException