1. 概述

本文深入探讨Java中多种文件读取方式的差异,重点对比getResourceAsStream()方法和FileInputStream类,并分析各自适用场景。同时介绍比FileInputStream更优的Files.newInputStream()方法,它在内存和性能方面更具优势。

我们将通过代码示例演示这些方法的具体使用方式,助你避开常见踩坑点。

2. 基础原理

在深入代码前,先理解getResourceAsStream()FileInputStream的核心区别及典型使用场景。

2.1. 使用getResourceAsStream()读取文件

getResourceAsStream()方法用于从类路径(classpath)读取文件:

  • 文件路径要求:必须相对于类路径
  • 返回类型InputStream对象
  • 典型场景: ✅ 读取配置文件 ✅ 加载属性文件 ✅ 访问应用打包资源

2.2. 使用FileInputStream读取文件

FileInputStream类用于从文件系统读取文件:

  • 文件路径要求:绝对路径或相对于当前工作目录
  • 性能警告:因使用终结器(finalizer)可能导致内存和性能问题
  • 推荐替代Files.newInputStream()(功能相同但性能更优)
  • 典型场景: ✅ 读取日志文件 ✅ 处理用户数据文件 ✅ 访问外部机密文件

3. 代码示例

通过实际代码演示getResourceAsStream()Files.newInputStream()的使用方法。

3.1. 使用getResourceAsStream()

创建工具类方法读取资源文件:

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

关键点:

  • 文件路径必须相对于类路径
  • 使用BufferedReader逐行读取
  • 异常处理返回null

3.2. 使用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;
    }
}

关键点:

  • 文件路径需绝对路径或相对项目根目录
  • 流处理方式与资源读取相同
  • 避免了FileInputStream的性能陷阱

4. 测试验证

通过对比测试展示两种方法的路径处理差异。

4.1. 读取类路径文件

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/main/resources/test.txt");
    assertNotNull(result);
    assertEquals(result, "Hello!\n" + "Welcome to the world of Java NIO.");
}

路径对比: | 方法 | 路径格式 | 示例路径 | |---------------------|------------------------|------------------------------| | readFileFromResource | 类路径相对路径 | /test.txt | | readFileFromFileSystem | 文件系统路径 | 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);
}

关键结论: ✅ Files.newInputStream()可读取任何文件系统路径 ❌ getResourceAsStream()仅限类路径内资源 ⚠️ 类路径外文件通过getResourceAsStream()读取将返回null

5. 总结

本文深入对比了Java中两种文件读取方式:

  • **getResourceAsStream()**:专精类路径资源读取,适合配置文件等打包资源
  • **Files.newInputStream()**:替代FileInputStream的最佳选择,用于文件系统文件访问

选择建议:

  1. 优先使用Files.newInputStream()处理文件系统文件
  2. 配置/资源文件首选getResourceAsStream()
  3. 避免在性能敏感场景使用FileInputStream

示例代码已托管在GitHub仓库,欢迎参考实践。


原始标题:Guide to getResourceAsStream() and FileInputStream in Java | Baeldung