【问题标题】:Different ways of loading a file as an InputStream将文件加载为 InputStream 的不同方式
【发布时间】:2010-10-15 03:28:03
【问题描述】:

两者有什么区别:

InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName)

InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)

InputStream is = this.getClass().getResourceAsStream(fileName)

什么时候比其他的更适合使用?

我要读取的文件在类路径中,作为读取文件的类。我的类和文件在同一个 jar 中并打包在一个 EAR 文件中,并部署在 WebSphere 6.1 中。

【问题讨论】:

    标签: java inputstream


    【解决方案1】:

    对于您传递的fileName 的解释方式存在细微差别。基本上,您有 2 种不同的方法:ClassLoader.getResourceAsStream()Class.getResourceAsStream()。这两种方法会以不同的方式定位资源。

    Class.getResourceAsStream(path) 中,路径被解释为您从中调用它的类的包的本地路径。例如,调用String.class.getResourceAsStream("myfile.txt") 将在您的类路径中的以下位置查找文件:"java/lang/myfile.txt"。如果您的路径以/ 开头,那么它将被视为绝对路径,并且将从类路径的根目录开始搜索。所以调用String.class.getResourceAsStream("/myfile.txt") 将在你的类路径./myfile.txt 中查看以下位置。

    ClassLoader.getResourceAsStream(path) 将所有路径视为绝对路径。所以调用String.class.getClassLoader().getResourceAsStream("myfile.txt")String.class.getClassLoader().getResourceAsStream("/myfile.txt") 都会在你的类路径中的以下位置查找文件:./myfile.txt

    每当我在这篇文章中提到一个位置时,它可能是您文件系统本身中的一个位置,也可能是相应 jar 文件中的一个位置,具体取决于您从中加载资源的 Class 和/或 ClassLoader。

    在您的情况下,您是从应用程序服务器加载类,因此您应该使用Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName) 而不是this.getClass().getClassLoader().getResourceAsStream(fileName)this.getClass().getResourceAsStream() 也可以。

    阅读this article 了解有关该特定问题的更多详细信息。


    Tomcat 7 及以下用户的警告

    这个问题的一个答案表明,我的解释对于 Tomcat 7 似乎是不正确的。我试图环顾四周,看看为什么会出现这种情况。

    所以我查看了 Tomcat 的 WebAppClassLoader 的源代码,了解了几个版本的 Tomcat。 findResource(String name)(最终负责生成请求资源的 URL)的实现在 Tomcat 6 和 Tomcat 7 中几乎相同,但在 Tomcat 8 中有所不同。

    在版本 6 和 7 中,实现不会尝试规范化资源名称。这意味着在这些版本中,classLoader.getResourceAsStream("/resource.txt") 可能不会产生与 classLoader.getResourceAsStream("resource.txt") 事件相同的结果,尽管它应该(因为 Javadoc 指定了)。 [source code]

    但在版本 8 中,资源名称被规范化以保证资源名称的绝对版本是使用的那个。因此,在 Tomcat 8 中,上述两个调用应该总是返回相同的结果。 [source code]

    因此,在 Tomcat 8 之前的版本上使用 ClassLoader.getResourceAsStream()Class.getResourceAsStream() 时必须格外小心。而且您还必须记住,class.getResourceAsStream("/resource.txt") 实际上调用了 classLoader.getResourceAsStream("resource.txt")(前导 @987654349 @ 被剥离)。

    【讨论】:

    • 我很确定 getClass().getResourceAsStream("/myfile.txt") 的行为与 getClassLoader().getResourceAsStream("/myfile.txt") 不同。
    • @BrianGordon:他们的行为并没有什么不同。实际上,Class.getResourceAsStream(String) 的 javadoc 说了以下内容:“这个方法委托给这个对象的类加载器。”,然后给出了一堆规则,说明它在委托给类加载器之前如何将相对路径转换为绝对路径。
    • @LordOfThePigs 看实际出处。如果您提供绝对路径,Class.getResourceAsStream 会去掉前导斜杠。
    • @BrianGordon:这使得它的行为与 ClassLoader.getResourceAsStream() 完全相同,因为后者将所有路径解释为绝对路径,无论它们是否以斜杠开头。所以只要你的路径是绝对的,这两种方法的行为都是一样的。如果你的路径是相对的,那么行为是不同的。
    • 我找不到getClassLoader()String,是错误还是需要扩展?
    【解决方案2】:

    使用MyClass.class.getClassLoader().getResourceAsStream(path) 加载与您的代码关联的资源。使用 MyClass.class.getResourceAsStream(path) 作为快捷方式,并用于打包在您的类包中的资源。

    使用Thread.currentThread().getContextClassLoader().getResourceAsStream(path) 获取属于客户端代码一部分的资源,而不是与调用代码紧密绑定。你应该小心这一点,因为线程上下文类加载器可能指向任何东西。

    【讨论】:

      【解决方案3】:

      在普通旧 Java 7 上使用普通旧 Java,并且没有其他依赖项可以证明差异......

      我将file.txt 放在c:\temp\ 中,并将c:\temp\ 放在类路径中。

      只有一种情况是两次调用之间存在差异。

      class J {
      
       public static void main(String[] a) {
          // as "absolute"
      
          // ok   
          System.err.println(J.class.getResourceAsStream("/file.txt") != null); 
      
          // pop            
          System.err.println(J.class.getClassLoader().getResourceAsStream("/file.txt") != null); 
      
          // as relative
      
          // ok
          System.err.println(J.class.getResourceAsStream("./file.txt") != null); 
      
          // ok
          System.err.println(J.class.getClassLoader().getResourceAsStream("./file.txt") != null); 
      
          // no path
      
          // ok
          System.err.println(J.class.getResourceAsStream("file.txt") != null); 
      
         // ok
         System.err.println(J.class.getClassLoader().getResourceAsStream("file.txt") != null); 
        }
      }
      

      【讨论】:

      • 非常感谢,对我来说只工作了'J.class.getResourceAsStream("file.txt")'
      【解决方案4】:

      这里的所有这些答案,以及this question 中的答案,都表明加载绝对 URL,如“/foo/bar.properties”,class.getResourceAsStream(String)class.getClassLoader().getResourceAsStream(String) 处理相同。情况并非如此,至少在我的 Tomcat 配置/版本(当前为 7.0.40)中并非如此。

      MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
      MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!
      

      对不起,我绝对没有令人满意的解释,但我猜想 tomcat 用类加载器做了肮脏的把戏和他的黑魔法并导致了差异。我过去一直使用class.getResourceAsStream(String),没有遇到任何问题。

      PS:我也通过here发布了这个

      【讨论】:

      • 也许 tomcat 决定不遵守规范,并且不将所有通过ClassLoader.getResourceAsStream() 的路径视为绝对路径?这是有道理的,因为正如上面一些 cmets 中提到的,Class.getResourceAsStream 实际上调用了 getClassLoader().getResourceAsStream`,但去掉了任何前导斜杠。
      • 查看 Java SE 的源代码后,我想我的答案是:Class.getResourceAsStream()ClassLoader.getResourceAsStream() 最终都会调用 ClassLoader.findResource(),这是一个默认实现为空的受保护方法,但其 javadoc 明确指出“类加载器实现应覆盖此方法以指定在何处查找资源”。我怀疑tomcat对这个特定方法的实现可能有缺陷。
      • 我还比较了Tomcat 7WebAppClassLoader.findResource(String name) 的实现与Tomcat 8 的实现,似乎有一个关键的区别。 Tomcat 8 通过添加前导 / 显式规范化资源名称(如果它不包含任何名称),这使得所有名称都是绝对的。 Tomcat 7 没有。这显然是 Tomcat 7 中的一个错误
      • 我在回答中添加了一段关于此的内容。
      【解决方案5】:

      在尝试了一些加载文件的方法都没有成功后,我记得我可以使用FileInputStream,效果很好。

      InputStream is = new FileInputStream("file.txt");
      

      这是将文件读入InputStream 的另一种方法,它从当前运行的文件夹中读取文件。

      【讨论】:

      • 它不是一个文件,它是一个资源。答案不正确。
      • @EJP 我最终得到了这个 SO 答案,在不知道文件和资源之间的区别的情况下寻找加载文件的方法。我不会删除我的答案,因为它可能对其他人有帮助。
      【解决方案6】:

      它的工作原理,试试这个:

      InputStream in_s1 =   TopBrandData.class.getResourceAsStream("/assets/TopBrands.xml");
      

      【讨论】:

        猜你喜欢
        • 2014-10-19
        • 2020-07-01
        • 2013-03-17
        • 2017-03-05
        • 1970-01-01
        • 2012-12-06
        • 2017-01-09
        • 1970-01-01
        相关资源
        最近更新 更多