题 如何从文件内容创建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();
    }
}

1214
2017-11-28 18:32


起源


任何人都可以用非常简单的方式向我解释NIO的用途吗?每次我读到它都会在第n次提到频道时迷路:( - OscarRyz
请记住,不能保证文件中的行分隔符不必与系统的行分隔符相同。 - Henrik Paul
你能不能插一个适当的尝试,最后关闭读者?实际上有人可能会使用此示例并在其代码中引入错误。 - Hans-Peter Störr
上面的代码有一个错误,即在最后一行添加额外的新行char。它应该类似于以下if(line = reader.readLine())!= null){stringBuilder.append(line); } while(line = reader.readLine())!= null){stringBuilder.append(ls); stringBuilder.append(line); } - Deep
Java 7介绍 byte[] Files.readAllBytes(file); 对于那些建议“一线”扫描仪解决方案的人:你不需要关闭它吗? - Val


答案:


读取文件中的所有文本

这是Java 7的一个紧凑,健壮的习惯用法,包含在一个实用程序方法中:

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中, BufferedReader 添加了一种新方法, lines() 生产一个 Stream<String>。如果 IOException 在读取文件时遇到它,它被包装在一个 UncheckedIOException从那以后 Stream 不接受抛出已检查异常的lambdas。

try (BufferedReader r = Files.newBufferedReader(path, encoding)) {
  r.lines().forEach(System.out::println);
}

还有一个 Files.lines() 做一些非常相似的方法,返回 Stream<String> 直。但我不喜欢它。该 Stream 需要一个 close() 呼叫;这在API上记录不足,我怀疑很多人甚至没有注意到 Stream 有一个 close() 方法。所以你的代码看起来非常相似,如下所示:

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

不同的是你有一个 Stream 分配给变量,我尝试避免这种做法,以便我不小心尝试两次调用流。

内存利用率

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

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

对于读取大文件,您需要为程序设计不同的设计,一个从流中读取一块文本,处理它,然后继续下一个,重复使用相同的固定大小的内存块。这里,“大”取决于计算机规格。如今,这个阈值可能是几千兆字节的RAM。第三种方法,使用 Stream<String> 如果您的输入“记录”恰好是单独的行,那么这是一种方法。 (使用 readLine() 的方法 BufferedReader 是程序等同于这种方法。)

字符编码

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

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

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

平台默认可从 Charset 本身:

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

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


1256
2017-11-28 18:56



从技术上讲,它在时间和空间上都是O(n)。定性地说,由于字符串的不变性要求,它在内存上相当困难;暂时在内存中有两个char数据副本,加上编码字节的空间。假设有一些单字节编码,它(暂时)需要为文件中的每个字符提供5个字节的内存。由于问题专门针对一个字符串,这就是我所展示的内容,但是如果您可以使用“解码”返回的CharBuffer,则内存需求要少得多。时间方面,我不认为你会在核心Java库中找到更快的东西。 - erickson
可能错字? NIO有一个名为java.nio.charset.Charset的Charset(而不是CharSet)类。这是CharSet应该是什么? - Jonathan Wright
注意:在执行了一些代码之后,我发现在使用此方法读取文件后,您无法可靠地删除该文件,在某些情况下这可能不是问题,但不是我的问题。可能与此问题有关: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154 ?我终于选择了没有遭受这个bug的Jon Skeet的命题。无论如何,我只是想为其他人提供信息以防万一...... - Sébastien Nussbaumer
@SébastienNussbaumer:我也遇到了这个问题。令人惊讶的是,该错误被标记为“无法修复”。这基本上意味着 FileChannel#map 通常是无法使用的。 - Joonas Pulakka
@SébastienNussbaumer:该漏洞已从Oracle / Sun Bug数据库中删除:“此错误无法使用。”谷歌缓存了该网站 webcache.googleusercontent.com/search?q=cache:bugs.sun.com/... - bobndrew


下议院 FileUtils.readFileToString

public static String readFileToString(File file)
                       throws IOException

使用默认编码将文件内容读入String   对于VM。该文件始终关闭。

参数:

  • file  - 要读取的文件,不能为null

返回:   文件内容,永远不会为空

抛出:     - IOException  - 出现I / O错误

以来:   Commons IO 1.3.1

该类(间接)使用的代码是:

IOUtils.java 下 Apache License 2.0

public static long copyLarge(InputStream input, OutputStream output)
       throws IOException {
   byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
   long count = 0;
   int n = 0;
   while (-1 != (n = input.read(buffer))) {
       output.write(buffer, 0, n);
       count += n;
   }
   return count;
}

它与Ritche_W使用的非常相似。


296
2017-11-28 18:44



它位于org.apache.commons.io.FileUtils类中 - Cyrille Ka
我也在使用FileUtils,但我想知道使用FileUtils或接受的nio答案有什么好处? - Guillaume
@Guillaume:最大的问题是你是否习惯依赖第三方库。如果您有Commons IO或 番石榴 在你的项目中,然后使用它(只是为了简化代码;否则可能不会有明显的区别)。 - Jonik
你的 网址 节目 FileUtils#readFileToString 成为 弃用 - Kevin Meredith
@KevinMeredith:它表明 readFileToString(File file) 被弃用。 readFileToString(File file,Charset encoding) 是替代品。 - Ian


这一页 非常精益的解决方案:

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

如果要设置charset


159
2017-09-16 20:02



\\ A有效,因为没有“文件的其他开头”,所以你实际上是读了最后一个令牌......这也是第一个。没试过用\\ Z.另请注意,您可以阅读任何可读的内容,例如Files,InputStreams,channels ...我有时会使用此代码从eclipse的显示窗口读取,当时我不确定我是在读一个文件还是其他文件。 .yes,classpath让我困惑。 - Pablo Grisafi
作为海报,我可以说我真的不知道文件是否以及何时正确关闭......我从来没有在生产代码中写这个,我只将其用于测试或调试。 - Pablo Grisafi
我认为它有1024个字符的限制 - Whimusical
扫描仪实现了Closeable(它在源上调用close) - 所以虽然优雅但它不应该是一个单行。缓冲区的默认大小为1024,但扫描程序将根据需要增加大小(请参阅扫描程序#makeSpace()) - earcam
对于带有a的空文件,这个失败了 java.util.NoSuchElementException。 - SpaceTrucker


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

private String readFile(String pathname) throws IOException {

    File file = new File(pathname);
    StringBuilder fileContents = new StringBuilder((int)file.length());
    Scanner scanner = new Scanner(file);
    String lineSeparator = System.getProperty("line.separator");

    try {
        while(scanner.hasNextLine()) {
            fileContents.append(scanner.nextLine() + lineSeparator);
        }
        return fileContents.toString();
    } finally {
        scanner.close();
    }
}

68
2017-11-28 19:00



我认为这是最好的方法。查看 java.sun.com/docs/books/tutorial/essential/io/scanning.html - Tarski
接受String的Scanner构造函数不会将字符串视为要读取的文件的名称,而是将其视为要扫描的文本。我总是犯这个错误。 : - / - Alan Moore
@Alan,好抓。我稍微编辑了Don的答案以解决这个问题(我希望)。 - Jonik
。fileContents.append(scanner.nextLine())附加(lineSeparator); - ban-geoengineering
将初始化语句更改为 Scanner scanner = new Scanner((Readable) new BufferedReader(new FileReader(file)));。否则,您可能只捕获部分文件。 - Wei Yang


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

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

// ...

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

由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版本的源代码不符。对于当前源,请参阅类 CharStreamsByteSource 和 CharSource 在 com.google.common.io 包。


63
2018-04-16 14:33



这段代码从long转换为int,这可能会弹出一些大文件的疯狂行为。有额外的空间,你在哪里关闭输入流? - M-T-A
@ M-T-A:流 是 关闭,注意使用 Closer 在 CharSource。答案中的代码不是实际的当前番石榴源。 - Jonik


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

50
2017-10-29 08:51



或者更简单: new String(Files.readAllBytes(FileSystems.getDefault().getPath( filename)));
要么 new String(Files.readAllBytes(Paths.get(filename))); :-) - assafmo
打得好,并保存下一个人谷歌搜索, Paths 显然是1.7+ 原样 FileSystems。 (该死的!) - ruffin
遗憾的是,这个答案没有更多的选票。我一直在寻找将文本文件转换为String的最快捷,最简单的方法。这就是它,如果我没有向下和向下滚动,我会错过它。 OP应考虑接受这个答案,将其移至顶部。 - Thorn
@Thorn这个答案有可怕的错误处理。不要在生产代码中使用此方法,或者更好:永远不要。 - xehpuk


如果您需要字符串处理(并行处理),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

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

另一个班轮示例

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

44
2017-11-28 19:56



.parallel()是在读取行之后还是之前发生的? - Istvan
真正的工作开始,因为终端操作collect(...)被调用。 Stream是逐行填充的。在处理(例如,过滤和映射)之前,不需要在存储器中读取整个文件。 - Andrei N
在选择非空线之前修剪? - Thorbjørn Ravn Andersen


该代码将规范化换行符,这可能是你真正想做的事情,也可能不是。

这是一个不能做到这一点的替代方案,而且(IMO)比NIO代码更容易理解(尽管它仍然使用 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();
    }        
}

44
2017-10-28 07:04



哪一个是“那个”代码? - OscarRyz
问题中的代码。 - Jon Skeet
请原谅我恢复这个旧的评论,但你的意思是传递一个名为“file”的String对象,还是应该是一个File对象呢? - Bryan Larson


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

从java 7开始,你就可以这样做。


42
2017-10-17 15:34



这应该被接受为答案 - 单行,没有外部库。 - Cherry
应该提到所需的导入:import java.nio.file.Files; import java.nio.file.Paths; - Maude