⛳ 如何使用 getResourceAsStream() 加载文件

🏭 一,简介

getResourceAsStream() 是java中用于加载资源文件的方法之一。

它是 ClassLoaderClass 类提供的方法,用于从类路径(ClassPath)中获取资源文件并返回一个输入流 (InputStream。这个方法常用于加载配置文件,模板文件,图片,音频等各种类型的资源。

方法签名:

InputStream getResourceAsStream(String name);

参数:

  • name : 表示要加载的资源文件的路径,可以是相对路径或绝对路径。对于相对路径,其起始点通常是调用 getResourceAsStream() 方法的类或类加载器的位置,需要看是YouClassName.class.getResourceAsStream()调用, 还是 YouCalssName.class.getClassLoader().getResourceAsStream()调用。具体细节看下文。

返回值:

  • 如果找到资源文件,则返回一个 InputStream 对象,允许你以字节流的方式读取资源文件的内容。
  • 如果找不到资源文件,则返回 null 。

注意事项:

  • getResourceAsStream()方法在查找资源文件时,会根据调用它的类或类加载器的位置来查找资源。如果资源文件不在相应的类路径下,可能会导致找不到资源的情况。
  • 路径参数一般以/开头表示从类路径的根目录开始查找资源,不以 / 开头表示从调用它的类所在的包路径开始查找资源。
    • 如何获取根路径和包路径:

      public class GetResourceTest {
          public static void main(String[] args) {
              /**
               * 获取根路径和包路径
               */
              URL resource = GetResourceTest.class.getClassLoader().getResource("a123.txt");
              String path = resource.getPath();
              System.out.println("根路径:" + path);
              URL resource1 = GetResourceTest.class.getResource("");
              String path1 = resource1.getPath();
              System.out.println("包路径" + path1);
          }
      }
      
      // 结果
      //根路径:/D:/Code/pracitice/untitled/target/classes/a123.txt
      //包路径/D:/Code/pracitice/untitled/target/classes/org/example/test/
      

      项目结构:

      image-20230722135948220

      image-20230722140005221

👣 二,示例用法

💻 2.1,不使用类加载器(ClassLoader

// 假设要加载名为"config.properties"的配置文件
InputStream inputStream = YourClassName.class.getResourceAsStream("/config.properties");

// 使用BufferedReader读取配置文件内容
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    // 处理异常
}

注:YourClassName是当前类的类名,用于定位类路径。可以通过这种方式加载任何类型的资源文件,并以输入流的形式读取其内容,无需关心资源文件的具体位置。

📢 2.2,使用类加载器(ClassLoader

/**
     *  zip 压缩文件部署
     * @throws IOException
     */
    @Test
    public void deployProcessByZip() throws IOException {
        // 定义zip输入流
        InputStream inputStream = this.getClass()  // getClass() 返回:表示此对象的运行时类的Class对象。
                .getClassLoader()   // 返回:装入此对象表示的类或接口的类装入器。
                .getResourceAsStream("process/process.zip");
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);

        // 流程部署
        Deployment deployment = repositoryService.createDeployment()
                .addZipInputStream(zipInputStream)
                .name("请假申请流程")
                .deploy();
        System.out.println("流程部署id: " + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

🎉 三,使用类加载器(ClassLoader)和不使用类加载器的区别

案例:

this.getClass()  // getClass() 返回:表示此对象的运行时类的Class对象。
    .getClassLoader()   // 返回:装入此对象表示的类或接口的类装入器。
    .getResourceAsStream("process/process.zip");


this.getClass()  // getClass() 返回:表示此对象的运行时类的Class对象
    .getResourceAsStream("process/process.zip");  
  1. this.getClass().getClassLoader().getResourceAsStream("process/process.zip"): 这种方式使用类加载器(ClassLoader)来加载资源文件。类加载器是用于加载类的机制,并且每个类加载器都有自己的类加载路径。getResourceAsStream方法的路径是相对于类加载器的根路径。因此,"process/process.zip" 这个路径将相对于类加载器的根路径来定位资源文件。

    在一些情况下,特别是当使用不同的类加载器(例如自定义类加载器)加载类时,this.getClass().getClassLoader().getResourceAsStream("process/process.zip") 可能更可靠,因为它直接使用类加载器的路径,不受当前类的位置的影响。

  2. this.getClass().getResourceAsStream("process/process.zip"): 这种方式使用当前类的类加载器来加载资源文件。getResourceAsStream方法的路径是相对于当前类所在的包路径(package path)。换句话说,它会在当前类的包路径下寻找资源文件。

    这种方式适用于当前类和资源文件在同一个包路径下的情况。如果资源文件与当前类在不同的包路径下,或者资源文件位于类加载器的根路径(Classpath根目录)下,这种方式可能会失败,因为它是相对于当前类所在的包路径来定位资源文件。

所以,总的来说,两种方式都可以加载资源文件,但要根据你的具体情况来选择哪种方式更合适。如果你需要在不同类加载器的情况下加载资源文件或者资源文件位于类加载器的根路径下,推荐使用this.getClass().getClassLoader().getResourceAsStream("process/process.zip");如果资源文件与当前类在同一个包路径下,那么 this.getClass().getResourceAsStream("process/process.zip") 是更直观的选择。

当我看到这里,还是有一点不明白,在this.getClass().getClassLoader().getResourceAsStream("process/process.zip")调用中,getResourceAsStream方法的路径是相对于类加载器的根路径。那么,这个类加载的根路径是哪里呢?

🚜 四,ClassLoader的根路径是在哪里?

类加载器的根路径,也称为Classpath根路径,是类加载器用于查找类和资源文件的基本路径。在Java中,有三种主要的类加载器:

  1. Bootstrap ClassLoader(启动类加载器):
    • 它是JVM的一部分,用于加载JVM自身的核心类,如java.lang.Objectjava.lang.String等。
    • 它没有确定的文件系统路径,它是用本地代码实现的,并且通常由JVM自动加载。
  2. Extension ClassLoader(扩展类加载器):
    • 它是由sun.misc.Launcher$ExtClassLoader表示的,负责加载Java的扩展库(extensions)。
    • 它的类路径通常是${java.home}/lib/ext/目录,其中${java.home}是JDK的安装目录。
  3. System ClassLoader / Application ClassLoader(系统类加载器/应用程序类加载器):
    • 它是由sun.misc.Launcher$AppClassLoader表示的,负责加载应用程序的类和资源文件。
    • 它的类路径通常是由java.class.path系统属性指定的,包括当前工作目录和所有被指定的JAR文件或目录。

对于系统类加载器,其类路径包含了Classpath根路径。在绝大多数情况下,Classpath根路径是项目构建(如Maven或Gradle项目)的src/main/resources目录。在这个目录下的资源文件,通过getClass().getResourceAsStream()或者getClass().getClassLoader().getResourceAsStream()来访问。

请注意,类加载器可以是层次结构的,当一个类加载器无法加载类或资源时,它会委托给父类加载器来尝试加载。因此,资源的查找顺序是从下往上,即从当前类加载器的类路径开始,然后向上层级查找,直到找到资源或到达Bootstrap ClassLoader。

💖 五,ClassClassLoader 加载资源的区别

📝 5.1,资源访问方式

资源(resource) 就是我们的程序需要访问的数据,例如图片、文本、视频、音频等等。

访问资源有两种方式:

  • Location dependent 位置相关
  • Location Independent 位置独立

🐾 5.2,Location dependent(位置相关)

所谓Location Dependent,就是我们对资源的访问方式受程序所在位置的影响。

例如,在Java中,使用本机绝对路径访问文件时,就是一种Location Dependent的访问方法,代码如下:

File file = new File("/root/project/resource/config.xml")

**注:**root 是项目的根路径。

如果项目中使用上述方式读取文件,当项目在其他目录或其他机器上部署和运行时,就需要修改上述代码中的文件路径,因此上述用法是LocationDependent的。

注意:

并不是说通过File类来访问资源一定是Location Dependent的,我们借助File也可以实现Location Independent的访问,例如我们可以给File构造器传入相对路径,这里的相对路径是相对于当前工作目录(System.getProperty(“user.dir”))的,所以如果要访问的资源是项目的一部分,File类搭配相对路径也可以实现Location Independent的访问。

☁ 5.3,Location Independent(位置无关)

实现Location Independent的资源读取最常用的就是Class或ClassLoader类中的如下方法:

  • URL getResource(String name)
  • InputStream getResourceAsStream(String name)
  • Enumeration<URL> getResources(String name)
  • getSystemResource, getSystemResources, getSystemResourceAsStream

其中,前两个方法是Class和ClassLoader类都有的,后面的方法只有ClassLoader类有。

借助这些方法,可以实现从classpath下读取资源,或者相对于当前class文件所在的目录读取资源。

Class 类获取资源的方法最终会调用ClassLoader类中对应的方法,那么,这两个类中获取资源的方法的区别在哪里呢?

区别在于ClassLoader类中的这两个方法仅支持相对于classpath的路径(开头不能加/,加了就获取不到classpath下的文件了),而Class类中的这两个方法除了支持相对于classpath的路径外(以/开头),还支持相对于当前class文件所在目录的路径(开头不加/)。
例子:

image-20230722134955576

public class GetResourceTest {
    public static void main(String[] args) {
        // 获取不到, 因为是相对于xxx/target/classes/com/resourcetest/的路径
        URL resource1 = GetResourceTest.class.getResource("config.xml");
        // 下面3个能获取到, 因为是相对于classpath的路径
        URL resource2 = GetResourceTest.class.getResource("/config.xml");
        URL resource3 = GetResourceTest.class.getClassLoader().getResource("config.xml");
        URL resource4 = GetResourceTest.class.getClassLoader().getResource("./config.xml");
        // 获取不到,无法被解析为相对于classpath的路径
        URL resource5 = GetResourceTest.class.getClassLoader().getResource("/config.xml");

        System.out.println(resource1); // null
        System.out.println(resource2); // 非null
        System.out.println(resource3); // 非null
        System.out.println(resource4); // 非null
        System.out.println(resource5); // null
    }
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐