Різні способи завантаження файлу як InputStream


216

Яка різниця між:

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

і

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

і

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

Коли кожен з них доцільніше використовувати, ніж інші?

Файл, який я хочу прочитати, знаходиться в класі, як мій клас, який читає файл. Мій клас та файл знаходяться в одній банці та упаковані у файл EAR та розгорнуті у WebSphere 6.1.

Відповіді:


289

Існують тонкі відмінності щодо того, як fileNameінтерпретується ваш передач. В основному у вас є два різні методи: ClassLoader.getResourceAsStream()і Class.getResourceAsStream(). Ці два методи по-різному знайдуть ресурс.

В Class.getResourceAsStream(path), шлях трактується як локальний шлях до пакету класу, з якого ви викликаєте. Наприклад , покликанням, String.getResourceAsStream("myfile.txt")буде шукати файл у вашому шляху до класів за наступною адресою: "java/lang/myfile.txt". Якщо ваш шлях починається з значка a /, він вважатиметься абсолютним шляхом і почне пошук з кореня classpath. Таким чином, дзвінок String.getResourceAsStream("/myfile.txt")буде розглядати наступне місце на шляху вашого класу ./myfile.txt.

ClassLoader.getResourceAsStream(path)вважатиме всі шляхи абсолютними. Так виклику String.getClassLoader().getResourceAsStream("myfile.txt")і String.getClassLoader().getResourceAsStream("/myfile.txt")обидва будуть шукати файл у вашому шляху до класів за наступною адресою: ./myfile.txt.

Кожен раз, коли я згадую про місце у цій публікації, це може бути місце у вашій файловій системі або у відповідному файлі jar, залежно від класу та / або ClassLoader, з якого ви завантажуєте ресурс.

У вашому випадку ви завантажуєте клас із сервера прикладних програм, тому його слід використовувати Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)замість this.getClass().getClassLoader().getResourceAsStream(fileName). this.getClass().getResourceAsStream()також буде працювати.

Прочитайте цю статтю для отримання більш детальної інформації про цю конкретну проблему.


Попередження для користувачів 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). [вихідний код]

Однак у версії 8 ім'я ресурсу нормалізується, щоб гарантувати, що абсолютна версія імені ресурсу є тією, що використовується. Тому в Tomcat 8 два описані вище дзвінки завжди повинні повертати однаковий результат. [вихідний код]

Як результат, ви повинні бути особливо обережними при використанні ClassLoader.getResourceAsStream()або Class.getResourceAsStream()на версіях Tomcat раніше, ніж 8. І ви також повинні мати на увазі, що class.getResourceAsStream("/resource.txt")насправді дзвонить classLoader.getResourceAsStream("resource.txt")(ведучий /позбавлений).


2
Я майже впевнений, що getClass().getResourceAsStream("/myfile.txt")поводиться інакше getClassLoader().getResourceAsStream("/myfile.txt").
Брайан Гордон

@BrianGordon: Вони не ведуть себе по-різному. Власне, javadoc для Class.getResourceAsStream (String) говорить про таке: "Цей метод делегує завантажувач класу цього об'єкта.", А потім дає купу правил про те, як він перетворює відносний шлях до абсолютного шляху перед делегуванням до класний навантажувач.
LordOfThePigs

@LordOfThePigs Подивіться на власне джерело. Class.getResourceAsStream позбавляє провідної косої риски, якщо ви надаєте абсолютний шлях.
Брайан Гордон

4
@BrianGordon: Це змушує його вести себе точно так само, як ClassLoader.getResourceAsStream (), оскільки останній трактує всі шляхи як абсолютні, незалежно від того, починаються вони з провідної косої риси чи ні. Тож поки ви шлях абсолютний, обидва методи поводяться однаково. Якщо ваш шлях відносний, то поведінка відрізняється.
LordOfThePigs

Я не міг знайти getClassLoader()з String, це помилка або необхідність розширення?
AaA

21

Використовуйте MyClass.class.getClassLoader().getResourceAsStream(path)для завантаження ресурсу, пов'язаного з вашим кодом. Використовуйте MyClass.class.getResourceAsStream(path)як ярлик і для ресурсів, упакованих в пакет вашого класу.

Використовуйте Thread.currentThread().getContextClassLoader().getResourceAsStream(path)для отримання ресурсів, що входять до коду клієнта, а не щільно межує з кодом виклику. Вам слід бути обережними з цим, оскільки завантажувач контекстного класу потоку може вказувати на що завгодно.


6

Звичайна стара Java на звичайній старій Java 7 і ніяких інших залежностей не демонструє різницю ...

Я поклав 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 ")"
abbasalim

3

Усі ці відповіді тут, а також відповіді в цьому запитанні дозволяють припустити, що завантаження абсолютних 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: Я також розмістив це тут


Можливо, tomcat вирішує не поважати специфікацію і не вважає всі пройдені шляхи ClassLoader.getResourceAsStream()абсолютними? Це правдоподібно, оскільки, як зазначалося в коментарях вище, Class.getResourceAsStreamнасправді викликає getClassLoader (). GetResourceAsStream`, але позбавляє будь-якого провідного косого кута.
LordOfThePigs

Після перевірки в вихідному коді Java SE, я думаю , що я тримаю відповідь: Як Class.getResourceAsStream()і в ClassLoader.getResourceAsStream()кінцевому рахунку , в кінцевому підсумку виклик , ClassLoader.findResource()який є захищеним методом реалізація якого по замовчуванням є порожнім, але чий Javadoc явно заявляє «Реалізація завантажувача класів повинні перевизначити цей метод , щоб визначити , де знайти ресурси ". Я підозрюю, що реалізація цього конкретного методу tomcat може бути помилковою.
LordOfThePigs

Я також порівнював реалізацію WebAppClassLoader.findResource(String name)в Tomcat 7 з реалізацією Tomcat 8 , і виявляється, що є ключова відмінність. Tomcat 8 явно нормалізує ім'я ресурсу, додаючи провідний, /якщо він не містить жодного, що робить усі імена абсолютними. Tomcat 7 ні.
Очевидно, що

Я додав абзац про це у своїй відповіді.
LordOfThePigs

0

Спробувавши декілька способів безрезультатно завантажити файл, я згадав, що міг би використовувати FileInputStream, який працював ідеально.

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

Це ще один спосіб зчитування файлу в. InputStreamВін читає файл з поточно запущеної папки.


Це не файл, це ресурс. Відповідь невірна.
Маркіз Лорн

1
@EJP Я закінчую цю відповідь, шукаючи способи завантаження файлу, не знаючи різниці між файлом та ресурсом. Я не збираюся видаляти свою відповідь, оскільки це може допомогти іншим.
António Almeida

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.