Java 中的 getResourceAsStream() 和 FileInputStream

1、概览

本文将带你了解 Java 中读取文件的不同方法之间的差异。主要介绍 getResourceAsStream() 方法和 FileInputStream 类,以及它们的用例。

先说结论,Files.newInputStream() 方法,由于其在内存和性能方面的好处,推荐用于替代 FileInputStream

2、基础知识

首先来了解一下 getResourceAsStream()FileInputStream 之间的区别以及它们的常见用例。

2.1、使用 getResourceAsStream() 读取文件

getResourceAsStream() 方法从 classpath 读取文件。传递给 getResourceAsStream() 方法的文件路径应相对于 classpath。该方法返回一个可用于读取文件的 InputStream

这种方法通常用于读取配置文件、properties 文件和其他与应用打包在一起的资源。

2.2、使用 FileInputStream 读取文件

FileInputStream 类用于从文件系统中读取文件。当需要读取未与应用打包在一起的文件时(本地磁盘),该类非常有用。

传递给 FileInputStream 构造函数的文件路径应该是绝对路径或与当前工作目录相对的路径。

FileInputStream 对象由于使用了 finalizersfinalize() 方法),可能存在内存和性能问题。FileInputStream 的更好替代方案是 Files.newInputStream() 方法,其工作方式相同。

本文示例中使用 Files.newInputStream() 方法从文件系统中读取文件。

这些方法通常用于读取文件系统中的外部文件,如日志文件、用户数据文件和 Secret 文件。

3、代码示例

让我们通过一个示例来演示 getResourceAsStream()Files.newInputStream() 的用法。

创建一个简单的工具类,使用这两种方法读取文件。然后,通过从 classpath 和文件系统中读取示例文件来测试这两种方法。

3.1、使用 getResourceAsStream()

首先,来看看 getResourceAsStream() 方法的用法。

创建一个名为 FileIOUtil 的类,并添加一个从指定资源中读取文件的方法:

static String readFileFromResource(String filePath) {
    try (InputStream inputStream = FileIOUtil.class.getResourceAsStream(filePath)) {
        String result = null;
        if (inputStream != null) {
            result = new BufferedReader(new InputStreamReader(inputStream))
              .lines()
              .collect(Collectors.joining("\n"));
        }
        return result;
    } catch (IOException e) {
        LOG.error("Error reading file:", e);
        return null;
    }
}

在此方法中,我们通过将文件路径作为参数传递给 getResourceAsStream() 方法来获取 InputStream。该文件路径应相对于 classpath。然后,使用 BufferedReader 读取文件内容。该方法逐行读取内容,并使用 Collectors.joining() 方法将它们拼接起来。最后,以 String 形式返回文件内容。

如果出现异常,例如找不到文件,则会被 catch 并返回 null

3.2、使用 Files.newInputStream()

接下来,使用 Files.newInputStream() 定义一个类似的方法:

static String readFileFromFileSystem(String filePath) {
    try (InputStream inputStream = Files.newInputStream(Paths.get(filePath))) {
        return new BufferedReader(new InputStreamReader(inputStream))
          .lines()
          .collect(Collectors.joining("\n"));
    } catch (IOException e) {
        LOG.error("Error reading file:", e);
        return null;
    }
}

在此方法中,我们使用 Files.newInputStream() 方法从文件系统中读取文件。文件路径应为绝对路径或项目目录的相对路径。与前一个方法类似,读取并返回文件内容。

4、测试

现在,通过读取一个示例来测试这两种方法。

注意观察,在不同情况下文件路径是如何传递给方法的。

4.1、从 Classpath 读取文件

首先,比较一下这些方法如何从 classpath 读取文件。

src/main/resources 目录下创建一个名为 test.txt 的文件,并在其中添加一些内容:

Hello!
Welcome to the world of Java NIO.

使用两种方法读取该文件并验证其内容:

@Test
void givenFileUnderResources_whenReadFileFromResource_thenSuccess() {
    String result = FileIOUtil.readFileFromResource("/test.txt");
    assertNotNull(result);
    assertEquals(result, "Hello!\n" + "Welcome to the world of Java NIO.");
}

@Test
void givenFileUnderResources_whenReadFileFromFileSystem_thenSuccess() {
    String result = FileIOUtil.readFileFromFileSystem("src/test/resources/test.txt");
    assertNotNull(result);
    assertEquals(result, "Hello!\n" + "Welcome to the world of Java NIO.");
}

我们可以看到,这两种方法都读取了文件 test.txt,并返回其内容。然后,比较文件内容,确保它们与预期值一致。这两种方法的区别在于作为参数传递的文件路径

readFileFromResource() 方法需要一个相对于 classpath 的路径。由于文件直接位于 src/main/resources 目录下,因此将 /test.txt 作为文件路径。

而,readFileFromFileSystem() 方法需要一个绝对路径或与当前工作目录相对的路径。我们传递 src/main/resources/test.txt 作为文件路径。或者,也可以传递文件的绝对路径,如 /path/to/project/src/main/resources/test.txt

4.2、从文件系统读取文件

在项目目录外创建一个名为 external.txt 的文件,并尝试使用这两种方法读取该文件。

创建测试方法,使用这两种方法读取文件:

@Test
void givenFileOutsideResources_whenReadFileFromFileSystem_thenSuccess() {
    String result = FileIOUtil.readFileFromFileSystem("../external.txt");
    assertNotNull(result);
    assertEquals(result, "Hello!\n" + "Welcome to the world of Java NIO.");
}

@Test
void givenFileOutsideResources_whenReadFileFromResource_thenNull() {
    String result = FileIOUtil.readFileFromResource("../external.txt");
    assertNull(result);
}

如上,我们传递的是 external.txt 文件的相对路径。readFileFromFileSystem() 方法直接从文件系统读取文件并返回其内容。

如果尝试使用 readFileFromResource() 方法读取文件,它将返回空值,因为文件不在 classpath 中。

5、总结

本文介绍了使用 getResourceAsStream() 从 classpath 读取文件与使用 Files.newInputStream() 从文件系统读取文件之间的区别。


Ref:https://www.baeldung.com/java-getresourceasstream-vs-fileinputstream