getResourceAsStream () vs FileInputStream

173

Próbowałem załadować plik do aplikacji internetowej i otrzymałem FileNotFoundwyjątek, gdy użyłem FileInputStream. Jednak korzystając z tej samej ścieżki, udało mi się załadować plik, gdy to zrobiłem getResourceAsStream(). Jaka jest różnica między tymi dwiema metodami i dlaczego jedna działa, a druga nie?

Vivin Paliath
źródło

Odpowiedzi:

256

java.io.FileI małżonki działa na lokalnym systemie plików na dysku. Główną przyczyną problemu jest to, że ścieżki względne w programie java.iosą zależne od bieżącego katalogu roboczego. To znaczy katalog, z którego uruchamiana jest maszyna JVM (w twoim przypadku: serwer WWW). Może to być na przykład C:\Tomcat\bincoś zupełnie innego, ale tym samym nie C:\Tomcat\webapps\contextname lub cokolwiek byś tego oczekiwał. W normalnym projekcie Eclipse to byłoby C:\Eclipse\workspace\projectname. Możesz dowiedzieć się o aktualnym katalogu roboczym w następujący sposób:

System.out.println(new File(".").getAbsolutePath());

Jednak katalog roboczy nie jest w żaden sposób sterowany programowo. Naprawdę powinieneś preferować używanie ścieżek bezwzględnych w FileAPI zamiast ścieżek względnych. Np C:\full\path\to\file.ext.

Nie chcesz na stałe kodować ani zgadywać bezwzględnej ścieżki w aplikacjach Java (internetowych). To tylko problem z przenośnością (tj. Działa w systemie X, ale nie w systemie Y). Normalną praktyką jest umieszczanie tego rodzaju zasobów w ścieżce klas lub dodawanie ich pełnej ścieżki do ścieżki klas (w środowisku IDE takim jak Eclipse jest to odpowiednio srcfolder i „ścieżka budowania”). W ten sposób możesz je złapać za pomocą ClassLoaderby ClassLoader#getResource()lub ClassLoader#getResourceAsStream(). Jest w stanie zlokalizować pliki względem „katalogu głównego” ścieżki klas, jak przez przypadek się zorientowałeś. W aplikacjach internetowych (lub innych aplikacjach, które używają wielu programów ładujących), zaleca się użycie w tym celu ClassLoaderzwróconej przez, Thread.currentThread().getContextClassLoader()aby można było również szukać „poza” kontekstem aplikacji internetowej.

Inną alternatywą w aplikacjach internetowych jest ServletContext#getResource()i jego odpowiednik ServletContext#getResourceAsStream(). Jest w stanie uzyskać dostęp do plików znajdujących się w webfolderze publicznym projektu aplikacji internetowej, w tym do /WEB-INFfolderu. Jest ServletContexton dostępny w serwletach metodą dziedziczoną getServletContext(), możesz go nazwać tak, jak jest.

Zobacz też:

BalusC
źródło
5
@khylo: related: stackoverflow.com/questions/7952090/…
BalusC
27

getResourceAsStream to właściwy sposób na zrobienie tego w przypadku aplikacji internetowych (jak już wiesz).

Powodem jest to, że odczyt z systemu plików nie może działać, jeśli spakujesz swoją aplikację internetową w WAR. To jest właściwy sposób pakowania aplikacji internetowej. W ten sposób jest przenośny, ponieważ nie jesteś zależny od bezwzględnej ścieżki pliku ani lokalizacji, w której jest zainstalowany serwer aplikacji.

duffymo
źródło
3
+1 - chociaż „nie działa” jest zbyt mocne. (Czytanie z systemu plików może działać, ale robienie tego przenośnie jest trudne ... i dużo więcej kodu, zwłaszcza jeśli zasób jest w JAR.)
Stephen C
1
duffy, bardzo miła odpowiedź i wyjaśniłeś, jaki był mój błąd, ale BalusC przeszedł do wielu szczegółów - myślę, że jego odpowiedź byłaby pomocna dla osób, które chciałyby również poznać szczegóły wewnętrzne. Mam nadzieję, że nie masz nic przeciwko temu, żebym zmienił zaakceptowaną odpowiedź na jego!
Vivin Paliath
@Stephen - nie sądzę, że „nie mogę pracować” jest zbyt mocne. Nawet coś tak prostego, jak wdrożenie na dwóch różnych serwerach z różnymi ścieżkami do serwera aplikacji, zepsuje to. Chodzi o to, że musisz sprawić, by twoja WOJNA była jak najbardziej niezależna. Twój punkt widzenia jest słuszny, ale zamierzam trzymać się swojego sformułowania.
duffymo
14

FileInputStream załaduje ścieżkę pliku przekazaną do konstruktora jako względną z katalogu roboczego procesu Java. Zwykle w kontenerze WWW jest to coś w rodzaju binfolderu.

getResourceAsStream()załaduje ścieżkę do pliku względną ze ścieżki klas aplikacji .

matowe b
źródło
12

FileInputStreamKlasa współpracuje bezpośrednio z podstawowym systemem plików. Jeśli danego pliku nie ma tam fizycznie, nie uda się go otworzyć. getResourceAsStream()Metoda działa inaczej. Próbuje zlokalizować i załadować zasób przy użyciu ClassLoaderklasy, do której jest wywoływany. Umożliwia to na przykład wyszukiwanie zasobów osadzonych w jarplikach.

Sztylet
źródło
Cóż, pliki w słoiku nadal są fizycznie „obecne” w systemie plików, tylko zawarte w innych plikach
mat b
1
Cóż, tak, oczywiście. Ale zazwyczaj nie są postrzegane jako niezależne jednostki w systemie plików, chyba że aplikacja wie o jarformacie pliku i jego konsekwencjach. A w Javie odpowiedni ClassLoadermoże mieć tę wiedzę, podczas gdy zwykły z FileInputStreampewnością nie.
Dirk
7

classname.getResourceAsStream () ładuje plik za pośrednictwem modułu ładującego klasy nazwa_klasy. Jeśli klasa pochodzi z pliku jar, jest to miejsce, z którego zostanie załadowany zasób.

FileInputStream służy do odczytu pliku z systemu plików.

Lachlan Roche
źródło
0

Jestem tutaj, oddzielając oba zastosowania, oznaczając je jako odczyt pliku (java.io) i odczyt zasobów (ClassLoader.getResourceAsStream ()).

Odczyt pliku - 1. Działa w lokalnym systemie plików. 2. Próbuje zlokalizować plik żądany z bieżącego katalogu uruchomionego JVM jako root 3. Idealnie sprawdza się w przypadku używania plików do przetwarzania w określonej lokalizacji, takiej jak / dev / files lub C: \ Data.

Odczyt zasobu - 1. Działa na ścieżce klasy. 2. Próbuje zlokalizować plik / zasób w bieżącej lub nadrzędnej ścieżce klasy modułu ładującego klasy. 3. Idealnie sprawdza się przy ładowaniu plików z plików spakowanych, takich jak war lub jar.

Aditya Bhuyan
źródło