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

1760

我已经使用下面的习语有一段时间了。至少在我访问的网站上,它似乎是最为广泛使用的。

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

8
有人能用非常简单易懂的语言解释一下NIO是什么吗?每次我阅读相关内容时,总是迷失在对通道的第N次提及中。 - OscarRyz
8
请记住,文件中的行分隔符未必与系统的行分隔符相同,并且需要进行调整。 - Henrik Paul
7
上述代码存在一个bug,在最后一行会多添加一个换行符。应该像下面这样修改:如果(line = reader.readLine())!= null { stringBuilder.append(line); } while((line = reader.readLine())!= null){ stringBuilder.append(ls); stringBuilder.append(line); } - Deep
31
Java 7 引进了 byte[] Files.readAllBytes(file); 方法。对于那些建议使用“一行代码” Scanner 解决方案的人:你不需要关闭它吗? - Val
@OscarRyz 对我来说最大的变化是NIO允许你监听许多端口而不为每个端口分配一个线程。这不是问题,除非你想向类B网络地址空间中的每台机器发送一个数据包(65k个地址)以查看存在的内容,Windows在大约20k时就会用完线程(在解决这个问题之前发现了这个问题——发现A/B网络,没有NIO很困难)。 - Bill K
显示剩余9条评论
35个回答

1826

读取文件中的所有文本

Java 11添加了readString()方法,以String形式读取小文件,保留行终止符:

String content = Files.readString(path, encoding);

对于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文档中对此的描述不够明确,我怀疑很多人甚至没有注意到 Stream 有一个 close() 方法。请务必像示例一样使用ARM代码块。

如果您使用的是除文件之外的其他来源,则可以使用BufferedReader中的lines()方法。

内存利用

如果相对于可用内存而言,您的文件足够小,则一次性读取整个文件可能没有问题。但是,如果您的文件太大,则逐行读取、处理并在转到下一行之前将其丢弃可能是更好的方法。以这种方式流处理可以消除总文件大小作为内存需求因素。

字符编码

原始帖子中缺少的一件事是字符编码。该编码通常无法从文件本身确定,并且需要元数据(例如HTTP标头)来传达这些重要信息。

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

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

平台默认字符集可以从 Charset 类本身获取:

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

有一些特殊情况下,平台默认设置可能是你想要的,但这种情况很少见。你应该能够证明自己的选择,因为平台默认设置不可移植。一个可能正确的例子是读取标准输入或写入标准输出。


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


1
内存利用取决于使用情况。如果您可以逐行处理文件,而不需要保留已处理的行,则通过行进行流式传输需要最少的内存。但是,当您需要将它们全部存储在内存中时,每行一个字符串对象的列表几乎一切都很便宜。开销甚至可能比在内存中具有字节数组和(单个)字符串还要大。如果满足某些先决条件,则第一个解决方案“Files.readString(…)”是仅能在需要额外内存的情况下工作的解决方案。 - Holger
@Holger Ah,“第一”由于编辑不再是第一了。我需要修补一下。 - erickson

398
如果你愿意使用外部库,请查看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);
}

190

一个基于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的博客


4
这段代码之所以有效,是因为不存在“文件的其他开头”,因此实际上读取的是最后一个标记……也是第一个标记。我从未尝试过使用 \Z。另外请注意,您可以读取任何可读取的内容,例如文件、输入流和通道等。有时候我会使用这段代码来从 Eclipse 的显示窗口读取,当我不确定是否正在读取某个文件时……是的,类路径让我感到困惑。 - Pablo Grisafi
21
Scanner实现了Closeable接口(它在源上调用close方法),因此虽然优雅,但不应该只有一行代码。缓冲区的默认大小是1024,但Scanner会根据需要增加缓冲区大小(请参见Scanner#makeSpace())。 - earcam

162
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

Java 7

String content = new String(Files.readAllBytes(Paths.get("readMe.txt")), StandardCharsets.UTF_8);

Java 11

String content = Files.readString(Paths.get("readMe.txt"));

81

如果你正在寻找一种不涉及第三方库(例如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();
    }
}

73

Guava有一个类似于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年10月删除。请改用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版本的源代码不匹配。请参见FilesCharStreamsByteSourceCharSource类,位于com.google.common.io包中。


这段代码将long类型转换为int类型,对于大文件可能会出现一些不可预知的行为。此外还存在多余的空格,你在哪里关闭了输入流? - Mohamed Taher Alrefaie
@M-T-A:流已关闭,请注意在CharSource中使用Closer。答案中的代码不是实际的、当前的Guava源代码。 - Jonik

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

14
或者 new String(Files.readAllBytes(Paths.get(filename))); :-) (这段代码是Java中读取文件并返回文件内容的方式之一。) - assafmo

52

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

1
Files.lines(Paths.get("file.txt")) 返回的流未关闭,存在资源泄漏。您应该将其包装在 try-with-resources 块中。 - tmoschou

51

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

这里有一个替代方法,它不会执行这个操作,并且(在我看来)比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();
    }        
}

1
请原谅我挖起这么老的评论,但是您是想传递一个名为“file”的字符串对象,还是应该使用File对象? - Bryan Larson
1
很好的回答。+1。但是这个答案已经有12年了。Java现在有try-with-resources。 - Harshal Parekh

32

收集了从磁盘或网络读取文件并转换成字符串的所有可能方法。

  • Guava: Google 使用类 ResourcesFiles

    static Charset charset = com.google.common.base.Charsets.UTF_8;
    public static String guava_ServerFile( URL url ) throws IOException {
        return Resources.toString( url, charset );
    }
    public static String guava_DiskFile( File file ) throws IOException {
        return Files.toString( file, charset );
    }
    

static Charset encoding = org.apache.commons.io.Charsets.UTF_8;
public static String commons_IOUtils( URL url ) throws IOException {
    java.io.InputStream in = url.openStream();
    try {
        return IOUtils.toString( in, encoding );
    } finally {
        IOUtils.closeQuietly(in);
    }
}
public static String commons_FileUtils( File file ) throws IOException {
    return FileUtils.readFileToString( file, encoding );
    /*List<String> lines = FileUtils.readLines( fileName, encoding );
    return lines.stream().collect( Collectors.joining("\n") );*/
}

public static String streamURL_Buffer( URL url ) throws IOException {
    java.io.InputStream source = url.openStream();
    BufferedReader reader = new BufferedReader( new InputStreamReader( source ) );
    //List<String> lines = reader.lines().collect( Collectors.toList() );
    return reader.lines().collect( Collectors.joining( System.lineSeparator() ) );
}
public static String streamFile_Buffer( File file ) throws IOException {
    BufferedReader reader = new BufferedReader( new FileReader( file ) );
    return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
Scanner类与正则表达式\A一起使用,该表达式匹配输入的开头。
static String charsetName = java.nio.charset.StandardCharsets.UTF_8.toString();
public static String streamURL_Scanner( URL url ) throws IOException {
    java.io.InputStream source = url.openStream();
    Scanner scanner = new Scanner(source, charsetName).useDelimiter("\\A");
    return scanner.hasNext() ? scanner.next() : "";
}
public static String streamFile_Scanner( File file ) throws IOException {
    Scanner scanner = new Scanner(file, charsetName).useDelimiter("\\A");
    return scanner.hasNext() ? scanner.next() : "";
}

  • Java 7 (java.nio.file.Files.readAllBytes)

public static String getDiskFile_Java7( File file ) throws IOException {
    byte[] readAllBytes = java.nio.file.Files.readAllBytes(Paths.get( file.getAbsolutePath() ));
    return new String( readAllBytes );
}

  • 使用 InputStreamReaderBufferedReader

public static String getDiskFile_Lines( File file ) throws IOException {
    StringBuffer text = new StringBuffer();
    FileInputStream fileStream = new FileInputStream( file );
    BufferedReader br = new BufferedReader( new InputStreamReader( fileStream ) );
    for ( String line; (line = br.readLine()) != null; )
        text.append( line + System.lineSeparator() );
    return text.toString();
}

这是一个使用主方法来访问上述方法的示例。

public static void main(String[] args) throws IOException {
    String fileName = "E:/parametarisation.csv";
    File file = new File( fileName );

    String fileStream = commons_FileUtils( file );
            // guava_DiskFile( file );
            // streamFile_Buffer( file );
            // getDiskFile_Java7( file );
            // getDiskFile_Lines( file );
    System.out.println( " File Over Disk : \n"+ fileStream );


    try {
        String src = "https://code.jquery.com/jquery-3.2.1.js";
        URL url = new URL( src );

        String urlStream = commons_IOUtils( url );
                // guava_ServerFile( url );
                // streamURL_Scanner( url );
                // streamURL_Buffer( url );
        System.out.println( " File Over Network : \n"+ urlStream );
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }
}

@see



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